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.
The short version is:
- Keep the real trace outside the repo.
- Save the active agent session path into
.git/hf-traces-active.json. - After each commit, upload that trace to a private bucket.
- 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:
hf auth login
hf buckets create your-hf-username/agent-traces --private --exist-okThe 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:
{
"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:
git rev-parse --git-path hf-traces-active.jsonThat 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:
#!/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);
}
NODESave that as .git/hooks/pre-commit and make it executable:
chmod +x .git/hooks/pre-commitThis 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:
- Reads
.git/hf-traces-active.json. - Finds the trace file on disk.
- Uploads it to a private Hugging Face bucket.
- Adds a git note to
HEAD.
#!/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:
chmod +x .git/hooks/post-commitFor 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?”
git log --notes=hf-traces -- src/routes/export.tsOr for one commit:
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.