Panout review packOverviewFinal memoMVPLive outputsJudge roundsAppendicesDecisions

Source: docs/research/2026-09-03-defensibility/appendices/D-session-commit-linkage.md

Does local session evidence predict commit rework, beyond git history?

Measured 2026-09-03. Read-only: no repo, log, or session file was modified. Only metadata was extracted from session logs (timestamps, cwd, tool names, event types, is_error flags, and short SHAs printed by git commit). No prompt text, assistant text, or file contents were read into the analysis or this report.

Headline: the question could not be answered. Not "no effect" — no measurement. Two independent structural blockers reduce the joint git x session sample to n=10 with a degenerate outcome (10/10 reworked, zero negative examples), so AUC is undefined for both the git-only and the git+session model and no delta exists to report.


1. Method

Session scan

Linkage

Short SHA -> git rev-parse in the repo root of the session cwd -> full SHA -> test whether the commit is an ancestor of the default branch. For commits not on the default branch, a recovery pass searched default-branch commit messages (since 2026-07-20) for the session commit's subject line, which catches GitHub squash-merges (the squash body lists the branch commits).

Rework

Identical to the prior git-only backtest (/tmp/panout-ideas/backtest/measure.py): take added line ranges from git diff C^ C --unified=0 (skipping lockfiles/binaries/minified, top 20 files by added lines), find the newest default-branch descendant E inside the window, then git blame --reverse --porcelain C..E -L <ranges>. Any attribution other than E means a line was modified or deleted inside the window.


2. Blocker 1: zero time overlap with the git-only backtest

Range
raw.json sampled commits2026-03-09 -> 2026-08-04
All local session logs2026-08-13 -> 2026-09-03

The backtest window is by construction commits 30-180 days old, so every commit in raw.json has a full 30-day forward window. Local session logs begin nine days after the newest raw.json commit. The retention horizon of the Claude Code session store here is ~21 days.

Linkage yield against raw.json: 0 of 2,859 commits (0.0%). Not one repo, not one commit, in either the agent or human class. This is a deterministic consequence of the two date ranges, not a matching failure: 49 distinct commit SHAs were extracted from session logs and none shares a prefix with any of the 2,859 sampled SHAs.

Session-log retention is also short enough that no commit observed in a session is yet 30 days old. The earliest session commit (2026-08-14) reaches a full 30-day window on 2026-09-13; the median one (2026-08-25) on 2026-09-24.


3. Blocker 2: the SHA an agent sees is usually not the SHA that lands

49 SHAs were extracted; 48 still resolve in a local repo (1 was garbage-collected or lived in a deleted worktree).

Repo (session cwd root)Session commitsOn default branchRecovered via squash bodyNever landed
/code/quora/work/poe2400832
/code/games/car-racer3300
/code/games/crossword1100
/code/personal/resume1100
/code/dotfiles1100
/code/jones1010
worktree dynorouter/t3code-08449d0e1001
total486 (12.5%)9 (18.8%)33 (68.8%)

In the PR-based repo (poe2, 2,811 default-branch commits since 2026-08-01) 0 of 40 session commit SHAs are ancestors of origin/main. GitHub squash-merge rewrites the commit, so the SHA the agent printed at commit time never enters the branch history the backtest measures. Subject-line recovery from squash bodies rescued 8 of those 40 (20%); the other 32 have no trace on origin/main under any subject and are not contained by any remote branch — abandoned, rebased away, or still unmerged after 8-20 days.

This blocker is independent of Blocker 1. Even with perfectly overlapping date ranges, direct SHA linkage would recover only ~13% of session commits, and the recoverable ones are systematically the squash-merged (i.e. landed, reviewed) subset — a selection effect, not a random sample.

Session provenance, for the record: 11 session groups produced commits; 13 of 262 transcripts contain a commit. 36 of 48 commits carry an agent trailer; 47 of 48 are authored by gareth@garethpaul.com.


4. What the 48 linked commits do show (features, well-powered)

Feature values are computed over the parent session plus all its subagent transcripts, counting only events at or before the commit's timestamp.

Featureminp25p50p75maxmeannonzero
rejections before commit000020.12 / 48
interrupts before commit000010.02 / 48
tool errors (is_error) before commit02414277.844 / 48
tool_use calls before commit52203003901820344.348 / 48
Edit/Write calls before commit020345352659.147 / 48
session minutes before commit02001541172071381286.148 / 48
commits in same session141116169.448 / 48
lines added (git)016392531389179.047 / 48
files touched (git)0234204.447 / 48

The proposed predictor is nearly absent from the data. Only 3 of 48 session commits (6.3%) had any rejection or interrupt before the commit. Across all 262 transcripts there are only 53 rejection and 31 interrupt events total, against 15,660 tool calls. Sessions here run in bypassPermissions mode, which removes the approval prompt that generates most rejection events. A binary "friction happened" feature with a 6% base rate cannot carry meaningful signal at any achievable sample size in this corpus.

Tool errors are the opposite: present in 44 of 48 (91.7%), median 4 per commit. That feature has usable variance — it is the outcome, not the feature, that is missing.


5. Rates and AUC (n=10, shortened window)

Since no linked commit is 30 days old, the rework test was re-run at a 7-day window on the landed subset. 11 landed commits had >= 7 days of forward history; 1 (2a4fc35, resume) had no measurable added ranges after skip filters.

sharepolinkreworked 7drejinttool errtool_useeditsdur (min)+linesfilesagent
aa908a7car-racerdirectyes001713223811082828131
9392b79car-racerdirectyes0018150146430671389201
e3a8e7ccar-racerdirectyes012718205267138303171
5eef56bcrossworddirectyes21151212147514621
7e8aaf7dotfilesdirectyes0021062367599191
0217b5a79bpoe2squashyes000220211921121100
43d1336bd3poe2squashyes001217320154147341
994d698a12poe2squashyes00195418725331
fbf225b4c5poe2squashyes00322424272822181
fb03e1136epoe2squashyes0032762959430141
2a4fc35resumedirectunmeasurable00150027951

Friction split

Groupnreworked 7drate
>= 1 rejection or interrupt before commit22100%
0 rejections and 0 interrupts88100%

No contrast is observable. With 2 exposed and 8 unexposed and a saturated outcome, this design has essentially zero power: even a true effect of any size would produce exactly this table.

Tool errors, reworked vs not reworked

Groupnmean tool errors before commitmean rejections+interrupts
reworked within 7d109.800.40
not reworked within 7d0undefinedundefined

AUC

ModelFeaturesnAUC
(a) git-onlylines added, files touched, hour of day, repo, agent flag10undefined
(b) git-only + session(a) + rejections, interrupts, tool errors, tool_use count, edits, session duration, commits-in-session10undefined
deltanot computable

AUC is a rank statistic over (positive, negative) pairs. The outcome vector is all-positive (10 reworked, 0 not), so the pair set is empty and AUC is undefined for any feature set, including git-only. No logistic regression was fit: with 10 observations, one outcome class, and 12 candidate features, the likelihood is unbounded and any reported coefficient would be an artifact. No bootstrap CI on an AUC delta can be produced, because the point estimate does not exist.


6. Is the 10/10 itself informative?

A control sample of default-branch commits from the same repos and the same date band (2026-08-10 to 2026-08-27), measured with the identical 7-day reverse-blame test:

Repocontrol nreworked 7drate
/code/quora/work/poe2392769.2%
/code/games/crossword252080.0%
/code/dotfiles251872.0%
/code/games/car-racer13753.8%
/code/jones251040.0%
/code/personal/resume100.0%
pooled control1288264.1%
Populationnrework rate95% CI (bootstrap, 1000 resamples)
control (unlinked default-branch commits)12864.1%56.2% - 72.7%
session-linked commits10100.0%100.0% - 100.0%
difference+35.9 pp+28.1 to +44.5 pp

Binomial: P(10/10 reworked | p = 0.641) = 0.0116.

The all-positive outcome is therefore not a window artifact — the 7-day base rate has plenty of negatives. But the difference is confounded and should not be read as an effect:

With n=10 and p=0.012 against a confounded comparison, this is a curiosity, not a result.


7. Secondary: time-to-rework vs session features (n=10)

All 10 reworked, so the only continuous outcome available is hours-to-first-rework (a lower bound, per the backtest's method note). Spearman rank correlation:

Session featurerho vs hours-to-rework
tool errors before commit-0.636
Edit/Write calls before commit-0.455
session duration before commit-0.418
tool_use count before commit-0.333
lines added (git)-0.224
rejections + interrupts before commit+0.406

Signs are in the direction "more in-session error -> reworked sooner", but at n=10 the two-sided threshold for rho at alpha=0.05 is about |0.65|; none of these clears it, and six correlations were computed without correction. Treat as unreportable.


8. Sample-size caveat

n is far too small for any delta to be meaningful, and the specific delta requested does not exist. Concretely:

The binding constraints are (i) ~21-day session-log retention against a 30-day rework window, and (ii) squash-merge SHA rewriting, which severs 100% of the SHA-level linkage in the highest-volume repo.


Artifacts

All under /tmp/panout-ideas/linkage/:

FileContents
scan.pysession JSONL scanner (metadata + SHA extraction)
sessions.json262 transcripts as event lists
link.pymatch against raw.json (result: 0)
resolve.py, resolved.jsonSHA -> repo, full SHA, default-branch ancestry
subjmatch.py, landed.jsonsubject-based landing recovery
build.py, features.jsonsession + git features per linked commit
rework.py, measured.json7-day reverse-blame rework outcome
baserate.py, baserate.jsoncontrol-sample 7-day base rate
stats.pyrates, AUC attempt, bootstrap CIs, Spearman