Contents
Merging
You merge agent work with plain git, as always. gitta runs inside git merge, git pull and git push to the protected branch, checks the combined code and, when it stops a merge, says why in the output.
What runs when you merge
Every way the protected branch changes is checked. gitta init adds four git hooks.
pre-merge-commitchecksgit mergeandgit pullbefore the merge commit is made.reference-transactionchecks a fast-forward fromgit mergeorgit pull,git pull --rebase, a cherry-pick, a rebase,git merge --squashwith its commit,git am, a reset andgit update-ref, before the branch moves. When the combined tests fail, the branch stays where it was. This hook comes with git 2.28 and later.post-mergerecords the result. With git older than 2.28, it checks a fast-forward and moves the branch back when the tests fail, gitta checks a rebase afterwards, andgitta initsays so.pre-pushchecks what you push to the protected branch.
The tests never run in the protected branch's own checkout, where a second merge could change the files under them. They run in a checkout of gitta's own. post-commit tests what an agent's merge would give as soon as the agent commits on its branch, and after every merge into the protected branch gitta tests the other agents' committed work against the new state in the background. The merge then reuses the result for the same code and test command, so git holds the branch lock for a moment only. gitta also sets core.filesRefLockTimeout to 30 seconds in the repository, unless you set it, so a second merge at the same moment waits for the lock instead of failing.
gitta follows core.hooksPath and keeps hooks you already have, which still run first. Your own commits on the protected branch run no tests. A commit there takes about 50 ms longer, and work on other branches is not slowed.
- Weakened checks. gitta compares the incoming changes with their starting point. It looks for deleted test files, fewer assertions, new skip or
.onlymarkers, CI told to ignore failures, lower coverage thresholds and new suppression comments. It also looks for security controls removed from existing code, such as a login, permission or CSRF check, a CSRF exemption added, or a certificate check switched off. See Security controls and Security notes. - Credentials. If the agent adds a line that looks like an API key, token, private key or a password set in the code, gitta reports it with the file and the line, as for a weakened check. Lines that were already in the file before the agent started do not count, and a setting name inside a text or a template is not a credential.
- Gate. Your test command runs on the combined code in a separate checkout. gitta links the installed dependencies from your main checkout into it (
node_modules,.venv,venvandtarget), so the gate uses the same tools as your own test run. Folders that you name with--linkare linked too. A fast-forward of your checked out branch is tested right in your checkout, because git has already written the combined files there. A tree that already passed is not tested again.
When the tests pass, git finishes the merge and gitta adds nothing to the output:
$ git merge --no-edit ci-level
Updating e109176..270c946
Fast-forward
bench/aidev-replay/analyze.py | 2 +-
bench/aidev-replay/test_wilson.py | 9 +++++++++
2 files changed, 10 insertions(+), 1 deletion(-)
create mode 100644 bench/aidev-replay/test_wilson.pyWhen gitta stops a merge
When the tests fail on the combined code, gitta stops the merge. It names the failing test, prints the first error line and, when a function the code calls changed on main, its new definition line.
$ git merge --no-edit ci99
Auto-merging bench/aidev-replay/analyze.py
gitta: tests fail on main with ci99 (test_ci99.T.test_wider); main unchanged, merge cancelled (ignore git's note). Fix it and merge again.
TypeError: wilson() takes 2 positional arguments but 3 were given
Now on main: bench/aidev-replay/analyze.py:23: def wilson(k, n, *, z=1.96):
Not committing merge; use 'git commit' to complete the merge.There is nothing to clean up. gitta cancels the stopped attempt as soon as git finishes, but only when nothing else is uncommitted in that checkout. If you had other uncommitted changes, it leaves them alone and asks you to run git merge --abort yourself. Once the agent has fixed its code, merge again.
A fast-forward that fails is stopped before the branch moves. gitta puts the files back as they were and keeps your other uncommitted changes:
$ git merge ff-break
Updating 7214229..2fa3ee7
Fast-forward
gitta: tests fail on main with ff-break (2 tests: tests.test_data.TestData.test_valid, tests.test_numbers.T.test_hex); main did not move. Fix it and merge again.
ValueError: invalid literal for int() with base 10: '0xDEADBEEF'
fatal: ref updates aborted by hookA push that fails the check sends nothing.
The stop is recorded as merge_stopped in the audit trail, and each checked merge as merge_checked.
When another agent changed something your code calls
gitta tells the agent about such a change while it works, see Warnings and sync. At merge time it writes a note to .gitta/gitta.log and lets the tests on the combined code decide. A changed meaning never stops a merge by itself.
When the gate cannot run
Sometimes the gate stops because a tool is missing, for example node_modules/.bin/tsc: not found. That is a setup problem, not a fault in the agent's work. gitta says which tool it did not find and records gate_environment in the audit trail. Install the project's dependencies in the main checkout, or link the folder with --link, then merge again. gitta check reports the same case as CANNOT_RUN.
Removed tests and weakened checks
gitta reports removed tests and weakened checks. It never asks. Each finding is printed in the merge output, the CI log, .gitta/gitta.log and the audit trail. The setting weakened_checks decides what happens next. With warn, the default for local hooks, the merge goes ahead:
$ git merge --no-edit quickfix
Updating 5453f99..053ebba
Fast-forward
gitta: quickfix 1 new skip/ignore/only marker(s) in tests/test_data.py.
gitta: merged anyway, because weakened_checks is warn. It is in the audit trail.
tests/test_data.py | 1 +
1 file changed, 1 insertion(+)That is what a person at a terminal sees. Inside an AI agent's tool call, gitta prints nothing for a warning, because it does not change the agent's next step. The finding goes to .gitta/gitta.log and the audit trail. gitta knows it runs inside an agent from CLAUDECODE, which Claude Code sets, or from GITTA_AGENT=1 or CODEX_SANDBOX. A block is always printed.
With block, the default in CI and in the pull request check, the merge stops:
$ git merge --no-edit drop-test
gitta: drop-test test file removed in tests/test_numbers.py.
gitta: drop-test was not merged: .gitta.toml sets weakened_checks to block; main unchanged, merge cancelled (ignore git's note). Restore what it removed.
Not committing merge; use 'git commit' to complete the merge.The setting lives in the committed .gitta.toml, so the team changes it with a reviewed commit. See Team policy. The audit trail records each case as warned or blocked, with the findings and who ran the merge.
Security controls
An agent that removes a security control from existing code is reported in the same way as a removed test. gitta names the control, the function that lost it and the new code that calls that function. See Warnings for the controls that gitta knows.
Security notes
gitta checks the lines each agent added for well-known security risks, see Risky code in an agent's own change. Medium notes are shown and recorded. High notes count as weakened checks, so weakened_checks decides whether the merge goes ahead. Choose the level with gitta init --security-block LEVEL. With high, the default, high notes count. With medium, every note counts. With none, notes are only reported.
Skipping the checks
git merge --no-verify skips gitta, as it skips any git hook. For a cherry-pick, rebase or reset, set GITTA_SKIP=1 for that one command. gitta records each skip as checks_skipped in the audit trail and writes a line to .gitta/gitta.log.
Optional commands
gitta land <agent> runs the same checks, merges and moves main forward by one commit in one command. gitta land --all lands every agent in dependency order, and gitta land <agent> --pr pushes the checked result to gitta/pr/<agent> and opens a pull request. In a repository that sets weakened_checks to block, a person can record an override with gitta approve <agent> <reason>. It covers only the current findings, runs only at a person's terminal, outside agent sessions, and the reason goes into the audit trail. Where an override must be enforced, use the pull request route with branch protection and a review by a person.
Change the gate
Set or change the gate with gitta init --gate "COMMAND". The command runs with sh in the root of the combined checkout. Exit code 0 means pass. Use the same command your CI uses, for example npm ci && npm test.