Contents
Controlled examples on real repositories
Three popular open-source projects, two agents in each, two reasonable changes. Plain Git merges every pair without a conflict. The merged code then crashes, fails to build, or quietly does the wrong thing.
The projects are unmodified copies of psf/requests, sindresorhus/ky and go-chi/chi from their main branches in October 2026. The changes are ours, made for this page and written the way an agent would write them, so these are controlled examples, not incidents from production.
| Project | Plain Git | After the merge | gitta |
|---|---|---|---|
| requests (Python) | Merges, no conflict | Crashes at run time | Warns the agent during the work |
| ky (TypeScript) | Merges, no conflict | The build fails | Warns the agent during the work |
| chi (Go) | Merges, no conflict | Wrong value, no error | Warns the agent during the work |
requests, the Python HTTP library, where a helper now returns nothing
One agent follows RFC 7231 and removes the old ISO-8859-1 default for text responses without a charset. get_encoding_from_headers now returns None for them. Its own change is correct.
# src/requests/utils.py, agent no-latin1-default
- if "text" in content_type:
- return "ISO-8859-1"
-
At the same time, another agent adds a debug helper that calls the same function and expects a string.
# src/requests/diagnostics.py, agent encoding-log (new file)
from .utils import get_encoding_from_headers
def describe_encoding(response):
"""One line for the debug log: which encoding the body will be decoded with."""
return "body encoding: " + get_encoding_from_headers(response.headers).lower()
Plain Git merges both. The first text/html response without a charset then crashes the helper:
AttributeError: 'NoneType' object has no attribute 'lower'With gitta, the second agent hears about it in its session, before it finishes:
Gitta: agent no-latin1-default changed `get_encoding_from_headers`, which your new code in src/requests/diagnostics.py calls. Check that your code still fits. Their version: src/requests/utils.py:569: def get_encoding_from_headers(headers: CaseInsensitiveDict[str]) -> str | None:
Handle it yourself and tell the user in one sentence.The agent makes its helper handle a missing encoding. When you merge, gitta runs your tests on the combined code, so a test that covers this case stops the merge before main changes.
ky, the TypeScript HTTP client: a field renamed for clarity
One agent renames the timeout field of the shared TimeoutOptions type to timeoutMs, so the unit is obvious. It updates every place that uses the field, and its build passes.
// source/utils/timeout.ts, agent timeout-ms
export type TimeoutOptions = {
- timeout: number;
+ /** Milliseconds. */
+ timeoutMs: number;
Another agent adds a longer deadline for uploads, written against the old field name. Its build passes too.
// source/utils/deadline.ts, agent upload-deadline (new file)
import type {TimeoutOptions} from './timeout.js';
/** Uploads get a longer deadline than ordinary requests. */
export const uploadTimeoutOptions = (fetch: TimeoutOptions['fetch']): TimeoutOptions => ({
timeout: 120_000,
fetch,
});
Plain Git merges both. The merged code no longer compiles:
source/utils/deadline.ts(5,2): error TS2561: Object literal may only specify known properties, but 'timeout' does not exist in type 'TimeoutOptions'. Did you mean to write 'timeoutMs'?CI would catch this one after the merge. gitta catches it while the second agent still works on the file:
$ gitta status
main is the branch gitta merges finished work into.
timeout-ms 2 files changed, ready to merge
upload-deadline 1 file changed, ready to merge
timeout-ms changed `source/utils/timeout.TimeoutOptions`, which upload-deadline's new code in source/utils/deadline.ts uses. Check that the two changes still work together.With a gate that builds the code, such as npm test, git merge stops on this build error before main moves.
chi, the Go router: shorter request IDs
One agent makes logs easier to read. GetReqID now returns only the counter of a request ID, for example 000042 instead of web-3/a1b2c3-000042. It updates the test that checks the ID, and its tests pass.
// middleware/request_id.go, agent short-request-ids
if reqID, ok := ctx.Value(RequestIDKey).(string); ok {
+ // Logs only need the counter: "host/abc123-000042" becomes "000042".
+ if i := strings.LastIndex(reqID, "-"); i >= 0 {
+ return reqID[i+1:]
+ }
return reqID
}
Another agent adds middleware that tells clients which host served them, read from the start of the same ID.
// middleware/served_by.go, agent host-header (new file)
func ServedBy(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
host := strings.SplitN(GetReqID(r.Context()), "/", 2)[0]
w.Header().Set("X-Served-By", host)
next.ServeHTTP(w, r)
})
}
Plain Git merges both. The code compiles and nothing crashes. The header is simply wrong:
--- FAIL: TestServedByExample (0.00s)
zz_served_by_example_test.go:16: X-Served-By = "000042", want "web-3"Only a test for exactly this case finds it. With gitta:
Gitta: agent short-request-ids changed `GetReqID`, which your new code in middleware/served_by.go calls. Check that your code still fits. Their version: middleware/request_id.go:83: func GetReqID(ctx context.Context) string {
Handle it yourself and tell the user in one sentence.The agent reads the new definition and takes the host from somewhere else. At merge time your tests on the combined code decide, as in the other two cases.
What the three have in common
Each change is reasonable alone, and each agent's own checks pass. The problem exists only in the combination, so neither agent can see it and plain Git cannot either. gitta compares what each agent changed with what each other agent's new code uses, while they work. The second agent learns about the change in time to adapt, instead of after the merge.
To try it in your own repository, follow the quick start. Measurements shows how gitta did with real agents.