Every useful coding-agent run leaves behind a trail.

There is the prompt I gave it. The files it opened. The commands it ran. The tests that failed. The weird wrong turn it took before finding the fix. The final diff is only the last artifact in that chain.

I think that trace should be attached to the code somehow.

Not necessarily committed into the repo as a giant JSONL file. Agent traces can contain prompts, terminal output, file snippets, local paths, and occasionally things you really do not want in main. But the link to the trace should be part of the development history.

Before

After

Right now, a lot of agent context evaporates. Maybe 10% lands in the code, 10% lands in the commit message, and 10% lands in a doc or PR comment. The other 70% is the working context: searches, failed attempts, shell output, review comments, and little decisions that never make it into the final diff.

Keeping the full trace changes that. The repo still gets the normal code and commit history, but it also gets a durable pointer to the whole run.

How it works

Keep the trace private, keep the pointer in git.

Excalidraw-style diagram showing agent hooks, a post-commit hook, a private Hugging Face bucket upload, and a git note pointing future agents to the trace. Excalidraw-style diagram showing agent hooks, a post-commit hook, a private Hugging Face bucket upload, and a git note pointing future agents to the trace.

The short version is:

  1. Keep the real trace outside the repo.
  2. Save the active agent session path into .git/hf-traces-active.json.
  3. After each commit, upload that trace to a private bucket.
  4. Attach the bucket URL to the commit as a git note.

Step 1: Create a Private Place for Traces

I do not want raw trace files in the repo. They can contain prompts, terminal output, local paths, snippets of private files, and other things that should not accidentally land in main.

So the durable artifact lives somewhere private:

bash
hf auth login
hf buckets create your-hf-username/agent-traces --private --exist-ok

The repo only keeps a pointer to that artifact.

Step 2: Have Codex Write Down the Active Trace

Codex hooks receive a JSON payload about the current session. I save that payload inside git metadata, not the working tree:

json
{
	"hooks": {
		"SessionStart": [
			{
				"matcher": "startup|resume|clear",
				"hooks": [
					{
						"type": "command",
						"command": "sh -c 'git rev-parse --is-inside-work-tree >/dev/null 2>&1 || exit 0; cat > "$(git rev-parse --git-path hf-traces-active.json)"'",
						"statusMessage": "Recording active agent trace"
					}
				]
			}
		],
		"UserPromptSubmit": [
			{
				"hooks": [
					{
						"type": "command",
						"command": "sh -c 'git rev-parse --is-inside-work-tree >/dev/null 2>&1 || exit 0; cat > "$(git rev-parse --git-path hf-traces-active.json)"'",
						"statusMessage": "Recording active agent trace"
					}
				]
			}
		],
		"Stop": [
			{
				"hooks": [
					{
						"type": "command",
						"command": "sh -c 'git rev-parse --is-inside-work-tree >/dev/null 2>&1 || exit 0; cat > "$(git rev-parse --git-path hf-traces-active.json)"'",
						"statusMessage": "Recording active agent trace"
					}
				]
			}
		]
	}
}

That goes in ~/.codex/hooks.json.

The important detail is the destination:

bash
git rev-parse --git-path hf-traces-active.json

That resolves to a file under .git/, so there is no new tracked file and nothing to accidentally commit.

The same shape works for other coding agents too. The git hook below only needs metadata with a trace_file, transcript_path, or transcriptPath; it does not really care which agent wrote the file.

Step 3: Use Pre-Commit Only as a Guard

I would not upload from pre-commit. The commit does not exist yet, so you do not have the final commit hash to attach the trace to.

If you want a pre-commit hook, make it a cheap sanity check:

bash
#!/usr/bin/env bash
set -euo pipefail

active="$(git rev-parse --git-path hf-traces-active.json)"
[ -s "$active" ] || exit 0

ACTIVE_TRACE_FILE="$active" node <<'NODE'
const fs = require("fs");
const path = require("path");

const active = JSON.parse(fs.readFileSync(process.env.ACTIVE_TRACE_FILE, "utf8"));
const rawTraceFile = active.trace_file || active.transcript_path || active.transcriptPath;
const cwd = active.cwd || process.cwd();
const traceFile = rawTraceFile ? path.resolve(cwd, rawTraceFile) : null;

if (!traceFile || !fs.existsSync(traceFile)) {
  console.error("agent trace metadata exists, but the trace file is missing");
  process.exit(1);
}
NODE

Save that as .git/hooks/pre-commit and make it executable:

bash
chmod +x .git/hooks/pre-commit

This catches stale metadata before the commit happens. It still does not upload anything.

Step 4: Upload in Post-Commit and Attach a Git Note

The real work happens in post-commit, because HEAD now points at the commit you just made.

This hook:

  1. Reads .git/hf-traces-active.json.
  2. Finds the trace file on disk.
  3. Uploads it to a private Hugging Face bucket.
  4. Adds a git note to HEAD.
bash
#!/usr/bin/env bash
set -euo pipefail

bucket="${HF_TRACE_BUCKET:-your-hf-username/agent-traces}"
notes_ref="${HF_TRACE_NOTES_REF:-hf-traces}"
active="$(git rev-parse --git-path hf-traces-active.json)"

[ -s "$active" ] || exit 0

metadata="$(
	ACTIVE_TRACE_FILE="$active" node <<'NODE'
const fs = require("fs");
const path = require("path");

let active;
try {
  active = JSON.parse(fs.readFileSync(process.env.ACTIVE_TRACE_FILE, "utf8"));
} catch {
  process.exit(0);
}

const rawTraceFile = active.trace_file || active.transcript_path || active.transcriptPath;
const cwd = active.cwd || process.cwd();
const traceFile = rawTraceFile ? path.resolve(cwd, rawTraceFile) : null;
if (!traceFile || !fs.existsSync(traceFile)) {
  process.exit(0);
}

const basename = path.basename(traceFile, path.extname(traceFile));
const match = basename.match(/([0-9a-f]{8,}(?:-[0-9a-f]{4,}){2,})/i);
const sessionId = active.session_id || (match ? match[1] : basename);
const agent = active.agent || (traceFile.includes("/.codex/") ? "codex" : "agent");
const clean = (value) =>
  String(value)
    .trim()
    .replace(/[^A-Za-z0-9._-]+/g, "-")
    .replace(/^-+|-+$/g, "")
    .slice(0, 120);

process.stdout.write([traceFile, clean(agent), clean(sessionId)].join("\t"));
NODE
)"

[ -n "$metadata" ] || exit 0

IFS=$'\t' read -r trace_file agent session_id <<<"$metadata"

repo="$(basename "$(git rev-parse --show-toplevel)")"
remote_path="$repo/sessions/$agent-$session_id.jsonl"
bucket_uri="hf://buckets/$bucket/$remote_path"
trace_url="https://huggingface.co/buckets/$bucket/tree/$remote_path"

hf buckets create "$bucket" --private --exist-ok >/dev/null
hf buckets cp "$trace_file" "$bucket_uri" >/dev/null

note="hf-trace: $trace_url"
existing_note="$(git notes --ref="$notes_ref" show HEAD 2>/dev/null || true)"

if ! grep -Fq "$note" <<<"$existing_note"; then
  git notes --ref="$notes_ref" append -m "$note" HEAD
fi

echo "agent trace: $trace_url"

Save that as .git/hooks/post-commit:

bash
chmod +x .git/hooks/post-commit

For a team, I would commit the script somewhere like scripts/git-hooks/post-commit and symlink it into .git/hooks/post-commit, because .git/hooks itself is local-only.

Step 5: Read the Trace Later

Now git history can answer a better question than “what changed?”

It can answer “what context produced this change?”

bash
git log --notes=hf-traces -- src/routes/export.ts

Or for one commit:

bash
git notes --ref=hf-traces show <commit>

That is the whole trick. The code still has a normal commit history. The heavy trace stays private on Hugging Face. The useful bit is that future agents get the trail instead of starting from scratch.