Contents

Audit trail

gitta records who started each agent, what it changed, what was checked, what was stopped and what was merged. Every record links to the one before it, so a change to the record is found.

Why an audit trail for AI coding agents

When agents write code, an auditor asks new questions. Which changes came from an agent? Who started it? Was the change tested with the other work merged at the same time? Were tests removed or checks skipped? Git history shows commits and authors. The gitta trail adds the agent, the checks and the decisions around each change, in one record that you can verify and export.

What is recorded

EventWhenContent
agent_startedA new agent worktree appearsTool, branch, starting commit, path
warningAn agent is told about another agent's change, or about a security note on its own new codeKind, symbol, file, the other agent and its machine. For a security note: the rule, severity, file and line
security_allowedAn allow marker or an [allow] entry covers a security noteThe rule, file, line and reason, and whether the agent's own change added the allow
merge_checkedA git merge, git pull or git push to the protected branch passes the checksThe target branch, the incoming commit, the git command, the files and the test command with its result and time
merge_stoppedgitta stops a merge, pull or push because the tests fail on the combined codeThe target branch, the incoming commit, the git command and the failing tests
checks_skippedA merge ran with --no-verify, or a command moved the protected branch with GITTA_SKIPThe target branch, the merge commit and the command
warnedA merge removes tests or weakens a check, and weakened_checks is warnThe findings, the setting and who ran the merge
blockedThe same, and weakened_checks is block, so the merge stopsThe findings, the setting and who ran the merge
checksBefore gitta land moves mainEach check and its result: weakened checks, security, security rules with each note and the threshold, credentials, changed meaning, merge and tests. Also the test command and its time
land_refusedA landing is stoppedThe check that stopped it, and why
landedgitta land moves mainOld and new commit, exact file versions, the agent's commits with authors and trailers such as Co-Authored-By, approvals
reached_mainWork arrives by another route, for example a merged pull request, a push or git mergeThe commits, authors, committers, trailers, and how many name an AI co-author
approvedA person records an optional override with gitta approveWho, the reason, the exact findings

Each event also records the person (Git name and email, operating system user) and the machine. No source code is recorded. gitta records paths, symbols, commits and results only.

The trail is on from gitta init. It is stored in .gitta/audit.jsonl, one event per line.

Read it

 Terminal
$ gitta audit
2026-10-22T14:43:57Z  agent_started ci-level       dev@workstation git worktree workspace on branch ci-level from e1091763d4
2026-10-22T14:43:57Z  agent_started ci99           dev@workstation git worktree workspace on branch ci99 from e1091763d4
2026-10-22T14:43:58Z  warning       ci99           dev@workstation HAZARD bench.aidev-replay.analyze.wilson in bench/aidev-replay/analyze.py (with ci-level)
2026-10-22T14:43:59Z  merge_checked ci-level       dev@workstation 
2026-10-22T14:44:00Z  merge_stopped ci99           dev@workstation 
2026-10-22T14:44:00Z  reached_main                 dev@workstation 
2026-10-22T14:44:01Z  merge_checked ci99           dev@workstation 
...

Filter by date, agent or event. --json shows every field of each event:

 Terminal
$ gitta audit --since 2026-10-01 --event merge_stopped
$ gitta audit --agent ci99 --json

Check that nobody changed it

Each event carries the SHA-256 hash of the event before it. If someone changes, removes or reorders an event, the chain breaks at that point.

 Terminal
$ gitta audit verify
gitta: audit trail intact: 39 event(s), every one linked to the one before
$ gitta audit verify   # after someone edited .gitta/audit.jsonl
gitta: audit trail BROKEN: event 29 (merge_checked, 2026-10-21T09:50:39Z) was changed after it was written

For a record that nobody on a machine can rewrite, use the team server. Each machine sends its events to the servers as they happen, and the servers keep a copy that the machine cannot change. gitta audit verify --team checks that copy.

Export it

 Terminal
$ gitta audit --csv > evidence.csv
$ gitta audit --json > events.jsonl
 evidence.csv, first lines
time,event,agent,person,email,machine,summary,hash
"2026-10-22T09:47:05Z","agent_started","worktree-detect-language","dev","[email protected]","workstation","Claude Code workspace on branch worktree-detect-language from a975ef6486","ff84c7a4670f0c4281377eb3435ffbdfb33c4a909ae25adb057952961cbfa2e6"
"2026-10-22T09:47:05Z","agent_started","worktree-translate-api","dev","[email protected]","workstation","Claude Code workspace on branch worktree-translate-api from a975ef6486","193096e09832a4f8ead6226bee2d721f4eacaac6613eed156076ddb0cf54de71"

Use CSV for spreadsheets and auditors. Use JSON lines for a log system or SIEM.

Send events to your log system

With gitta Enterprise, the team server sends each audit event to your log system the moment it stores it. Turn on one or more outputs with a setting on the server. Each event keeps its seq, prev and hash fields, so you can still check the chain in your log system. The server adds repo and severity, and gitta audit verify leaves those two out of the hash.

Each output has its own queue on disk, in the server's data folder. When your log system is slow or down, the server keeps the events in that queue and sends them later, in order, also after a restart. It tries again after 1 second, then waits twice as long each time, up to 1 minute. Merges, landings and agents never wait for a log system. The server also keeps its own copy of every event, so gitta audit --team works while a log system is down.

SettingUse
GITTA_LOG_STDOUT=1 or --log-stdoutOne JSON line per event on standard output. Docker, Kubernetes, Google Cloud Logging, Amazon CloudWatch and Azure Monitor collect it with no extra agent.
GITTA_SYSLOG or --syslog URLSyslog in RFC 5424 format. Use udp://host:514, tcp://host:601 or tls://host:6514.
GITTA_SYSLOG_CA or --syslog-ca FILEA PEM file with your own certificate authority, for a syslog server with a private certificate.
GITTA_LOG_WEBHOOK or --log-webhook URLAn HTTPS address that takes events in batches of up to 100.
GITTA_LOG_WEBHOOK_FORMATjson (the default, a JSON array of events), splunk for the Splunk HTTP Event Collector, or datadog.
GITTA_LOG_WEBHOOK_TOKEN or --log-webhook-token-file FILEThe token for the receiver. gitta sends it as Authorization: Splunk, DD-API-KEY or Authorization: Bearer, to match the format.
GITTA_LOG_WEBHOOK_HEADEROne more header for your own endpoint, written as Name: value.
GITTA_LOG_WEBHOOK_CAA PEM file with your own certificate authority, for an endpoint with a private certificate.
GITTA_LOG_QUEUE_MBThe size of each queue on disk. The default is 256 MB.

Syslog

Each event is one message with facility 13 (log audit). The event name is the message ID, the chain fields are structured data, and the full event is JSON in the message body. Blocked merges and refused landings have severity warning. Over TCP and TLS, each message is framed with its length (RFC 6587), which rsyslog, syslog‑ng and most SIEM collectors read as is.

 Terminal
$ export GITTA_SYSLOG=tls://syslog.example.com:6514
$ export GITTA_SYSLOG_CA=/etc/gitta/company-ca.pem
$ gitta server --data /data
 One message
<108>1 2026-10-22T09:47:05Z workstation gitta - land_refused [gitta@32473 seq="12" prev="9c1e…" hash="4b7a…" source="5f0c…" repo="github.com/acme/shop" agent="leaky" person="Dev" email="[email protected]"] {"event":"land_refused",…}

Google Cloud Logging

Cloud Run and GKE send standard output to Cloud Logging. Cloud Logging reads the severity field of each line, so blocked merges show as warnings.

 Terminal
$ gcloud run services update gitta --region europe-north1 --update-env-vars GITTA_LOG_STDOUT=1

Find the events in the Logs Explorer with this query.

 Terminal
resource.type="cloud_run_revision"
jsonPayload.event="landed"

Amazon CloudWatch

On ECS or Fargate, give the container the awslogs log driver and set GITTA_LOG_STDOUT. CloudWatch Logs Insights reads the JSON fields.

 Task definition, container part
"environment": [{"name": "GITTA_LOG_STDOUT", "value": "1"}],
"logConfiguration": {
  "logDriver": "awslogs",
  "options": {"awslogs-group": "/gitta/audit", "awslogs-region": "eu-north-1", "awslogs-stream-prefix": "gitta"}
}
 Logs Insights
fields time, event, agent, actor.name, repo
| filter severity = "WARNING"
| sort time desc

Azure Monitor

Azure Container Apps sends standard output to your Log Analytics workspace.

 Terminal
$ az containerapp update --name gitta --resource-group gitta --set-env-vars GITTA_LOG_STDOUT=1
 Log Analytics
ContainerAppConsoleLogs_CL
| where ContainerAppName_s == "gitta"
| extend e = parse_json(Log_s)
| where e.event == "blocked"

Kubernetes and Docker

Set GITTA_LOG_STDOUT=1 in the container. The log collector of your cluster, such as Fluent Bit or the agent of your cloud, then sends each line on.

 Terminal
$ docker run -d --name gitta -e GITTA_LOG_STDOUT=1 -e GITTA_TOKENS -v gitta-data:/data -p 8080:8080 europe-north1-docker.pkg.dev/gitta-io-prod/enterprise/gitta-server:0.1.0
$ docker logs -f gitta

Splunk

Create an HTTP Event Collector token in Splunk, then point the server at the collector. Each event arrives with source gitta and source type _json.

 Terminal
$ export GITTA_LOG_WEBHOOK=https://splunk.example.com:8088/services/collector/event
$ export GITTA_LOG_WEBHOOK_FORMAT=splunk
$ export GITTA_LOG_WEBHOOK_TOKEN="$SPLUNK_HEC_TOKEN"
$ gitta server --data /data

Datadog

Use the logs intake address of your Datadog site and an API key. Events arrive with source gitta and a status that follows the severity.

 Terminal
$ export GITTA_LOG_WEBHOOK=https://http-intake.logs.datadoghq.com/api/v2/logs
$ export GITTA_LOG_WEBHOOK_FORMAT=datadog
$ export GITTA_LOG_WEBHOOK_TOKEN="$DD_API_KEY"
$ gitta server --data /data

Your own endpoint

With the json format, the server sends a POST with a JSON array of events. Any 2xx answer counts as received. Any other answer, or no answer, makes the server try again later.

 Terminal
$ export GITTA_LOG_WEBHOOK=https://logs.example.com/gitta
$ export GITTA_LOG_WEBHOOK_HEADER="X-Team: platform"
$ export GITTA_LOG_WEBHOOK_TOKEN="$LOGS_TOKEN"

From one laptop

Without a team server, gitta audit --follow prints each new event as gitta writes it. Add --json to get one JSON line per event and send it on with any tool. --agent and --event filter the stream, and --since prints the earlier events first.

 Terminal
$ gitta audit --follow --json >> ~/gitta-audit.jsonl
$ gitta audit --follow --json | logger --tag gitta
$ gitta audit --follow --event landed

Notes on landed commits

Each commit that gitta land makes gets a Git note with the agent, the tool, who landed it and the checks. Notes are a standard Git feature and do not change the commit.

 Terminal
$ git notes --ref=gitta show main | head -14
{
  "gitta": 1,
  "agent": "quickfix",
  "tool": "git worktree",
  "landed_by": {
    "name": "dev",
    "email": "[email protected]",
    "os_user": "dev",
    "machine": "workstation"
  },
  "checks": [
    {
      "check": "weakened checks",
      "result": "none found"
  ...

To share notes with your team, push them with git push origin refs/notes/gitta.

SOC 2 and ISO 27001

gitta gives evidence for the controls that auditors ask about when AI agents write code. The table maps each question to the events that answer it.

Question an auditor asksEvidenceSOC 2ISO/IEC 27001:2022
Who made this change, and was an AI agent involved?agent_started, landed, reached_mainCC8.18.32, 8.4
Was the change tested before it reached production code?merge_checked, merge_stopped, checksCC8.18.29, 8.25
Were checks weakened or skipped, and who merged it?warned, blocked, checks_skipped, approvedCC8.1, CC6.88.32, 8.28
Were security controls weakened, or risky code added, and who merged it?warning, checks, approved, security_allowedCC8.1, CC6.88.28, 8.32
Can secrets end up in the code?warned and blocked for credentialsCC6.18.28
Are conflicts between parallel changes found?warningCC7.28.16
Can the record be trusted?Hash chain, gitta audit verify, server copiesCC7.28.15, 5.33

To map these records to SOC 2 and ISO 27001 controls in more detail, see Compliance. For what gitta checks in each change, see Security and data.