Friction or wins in how Sebastian and the interactive agent work together —
not runner/code process, that's harvest-ideas.jsonl. Logged as it happens,
same "don't wait to be asked" rule as harvest. Reviewed periodically (see
CLAUDE.md) and validated, recurring lessons get folded into CLAUDE.md
directly rather than left to accumulate here indefinitely.
This session covered VISION items 5/6 across many build-babysit cycles
(stalls, retries, spec corrections) and got compacted at least once, but
never handed off to a fresh session — not because the work required staying
in one session, but because starting over felt risky: would a new session
actually pick up cleanly from runner status + the task list + CLAUDE.md,
or lose something only visible in the transcript?
Why it matters: CLAUDE.md is meant to be the thing that makes a new
session as good as a resumed one. If that's not trusted in practice, the
tool isn't doing its job, and sessions run needlessly long instead.
Promoted: CLAUDE.md now states this explicitly as a standing rule.
Two 429s and two NEEDS_CONTEXT stalls hit one slice in a row; each time the actual cause was traced (rate-limits.json, grepping tests, reading the session transcript) instead of blind-retried. Worth keeping as the default for any repeated failure, not just build stalls.
Spec runs and builds (5–20+ min) were launched via nohup ... & and waited
on in the background, so mid-turn questions got answered immediately instead
of after a long blocked wait. Kept doing this.
Session ended at a clean boundary: main green, wire-block-gate-categories
landed, a pre-existing CHECK_SPEC_DIFF_SCOPE env-leak bug (found only
because it blocked that land) fixed and harvested separately. Nothing here
depends on this transcript — VISION/gates-and-scheduling.md now indexes
today's three new gate-classification findings, and runner status /
harvest-ideas.jsonl carry the rest.
What the next session should raise with Sebastian before drafting
anything: RISK_PATHS, BLOCK_GATE_CATEGORY_PATHS, and
REVIEWER_SKIP_CATEGORIES are now three separately-maintained hardcoded
dicts with the same flaw (Python-specific, silent zero coverage for a
non-Python project 2) — the classification artifact's shape has always
been undecided (VISION doc's own "left open" section), and drafting
another one-off migration slice would just add a fourth ad-hoc shape.
Decide the shape once, then implement.
Fixed and harvested check-spec-diff-scope-leaks-into-slice-marks-fixture-tests
(the bug) unprompted, per the standing rule. But the recovery arc around
it — runner land re-running QA against a worktree that had gone stale
relative to main mid-session, and a .git/index.lock collision on the
retry after that — sat unharvested until Sebastian asked "harvest logged
already? this is not the first time." Both turned out genuinely
harvest-worthy (land-qa-gate-runs-against-stale-worktree-base,
land-git-retry-window-too-short-for-real-contention), and neither was a
duplicate of the bug I'd already logged.
Why it matters: the standing rule ("every failure... gets logged
immediately... don't wait to be asked") reads as one incident, one harvest
call — but a multi-step recovery arc is several distinct findings, and I
stopped at the first one until prompted. Not promoting to CLAUDE.md yet
(one sighting) — logging so a second occurrence gets caught as a pattern
instead of rediscovered.
first real 4-way parallel build surfaced a new git-contention family
Session did the requested harvest run first (closed 2026-08-20→08-23,
3 promotions), then a friction sweep of the last 6 runner sessions (found
nothing unlogged), then drafted and built the 3 promoted specs plus the
2-sub-slice state_paths.py generalization VISION already planned —
6 specs total once wire-skip-categories split off from
project-owned-skip-categories to dodge a Surface-cap bust from
sessions.py's own hub-file tax.
Landed on main: blast-radius-honors-tier, project-owned-skip- categories, state-paths-project-scoped (project_state_paths.py, a
new module — deliberately NOT an edit to state_paths.py, see that
spec's own Decisions for why). The third one's land hit a real squash-
merge conflict (its worktree predated the other two lands, all three
shared test_runner.py in Surface) — resolved by hand (both slices'
test sections are non-overlapping, kept both, verified qa-green), landed
via a manual commit + runner mark-direct, then remove_slice to clear
the ledger (mark-direct only touches legacy-marks.tsv, never
state.json — same gap HARVEST-LOG's 2026-07-18 entry already named for
manual lands). Worktree removed, ledger now shows BUILT correctly.
Still open, next session's queue:
claim-check-dispatch— CRASHED, twice, same failure (git commitinside its own worktree hitindex.lock, both times right after the IMPLEMENTER phase checkpoint). Root cause not confirmed but a strong lead (see harvest idconcurrent-worktree-checkpoint-commit-index- lock) — likely the dispatched agent's own parallel Bash/git tool calls, not cross-slice interference (each worktree has its own index, verified). Nothing was contending when checked after the fact both times — safe to justrunner runit again. Its worktree still exists at/Users/sebastian/ai/redlich/2026-08-23-claim-check-dispatch.wire-skip-categories— spec draft isgit stashed (git stash popto get it back), not yet run throughspec run/approve. Was blocked onproject-owned-skip-categorieslanding (now landed) — safe to proceed.state-paths-status-land— already approved and committed to main, never built. Was blocked onstate-paths-project-scopedlanding (now landed) — safe torunner runit.- This was the first time 4
runner runbuilds were launched truly in parallel — 3 of 4 crashed on the git-lock issue above, none of it data-loss, all recoverable, but worth reading if it recurs (harvest id above has a refined hypothesis but no transcript-confirmed root cause yet).
Also this session: fixed the running devcontainer's missing tmux
by hand (sudo apt-get install tmux, matches the Dockerfile's own
already-landed fix — the container itself was never rebuilt) — this
unblocked unsnooze's auto-resume, confirmed working overnight for a
genuine rate_limit-kind stop. A different stop kind (kind=overload,
logged in /home/vscode/.unsnooze/unsnooze.log) was NOT auto-resumed —
unsnooze's hook only logged detecting it, never tracked or resumed it;
logged as harvest id unsnooze-overload-stop-not-auto-resumed, unsure
if updating past the installed 1.16.1 (1.17.0 available) covers this.
Decided, not yet acted on: project 2 (the multi-project plan's next
real step) should be a genuinely different, non-Python stack — Sebastian's
call, nothing built toward it yet. VISION/multi-project-onboarding.md
still has the concrete next-step plan; re-read it fresh rather than
trusting this entry's specifics past its shelf life.
Uncommitted, expected: state/runner/{costs,harvest-ideas,rework}.jsonl
— routine ledger churn, safe to leave dirty per land.py's own
_EXPECTED_DIRTY convention; will get swept into the next harvest close.
BUILT and landed
Resumed b3c5a20e's handoff directly (confirmed via git log it really
was the last session — no commits after its own handoff commit) and
worked its stated queue to completion:
state-paths-status-land— was already approved, never built; ran clean (1735 tests), landed as5668607.claim-check-dispatch— was CRASHED twice on the git-lock issue that session's entry flagged; re-runner runresumed from checkpoint and completed clean, no manual ledger/lock surgery needed. Landed asfcbe51b.wire-skip-categories— popped the stashed draft and ran it throughspec run. Hit a real structural wall along the way:sessions.py+phases.pymust land together (the no-silent-default Decision on the newskip_categoriesparameter rules out a safe temporal split), and the mechanical Surface caller-scan genuinely required 9 files againstcheck-spec'sSURFACE_CAP=8— not a false positive, verified each entry by caller-count before accepting it. Asked Sebastian how to proceed (widen the cap generally, split the slice further, or override for this one spec); he chose "land as one slice, override the cap." BumpedSURFACE_CAPto 9 for exactly therun → approve → build → landwindow, then reverted it to 8 immediately after landing (1a859aa) — safe to revert unconditionally becausecheck-spec's shipped-spec exemption (aSlice:trailer inbuilt-slices) is permanent and cap-independent once a spec has landed.check-specconfirmed green repo-wide afterward with the cap back at 8.
Git-lock contention, three more sightings: hit .git/index.lock
races directly against the shared main/ worktree three separate times
this session — twice during git stash push (stashing the spec draft,
then the check-spec cap edit, to unblock the other two lands sitting
behind them), once during wire-skip-categories's own runner land
commit step. All three were transient (no process running, no lock file
left, content verified identical before touching anything) and cleared
with a plain retry — no data lost. The third one matches an
already-logged, already-recurring harvest idea almost exactly
(land-git-retry-window-too-short-for-real-contention: land.py's
_run_git_retrying retry budget is ~0.75s over 5 attempts, too short
for a real concurrent holder on shared main) — logged as a third
sighting rather than a new idea. The two stash-time races are a
variant not previously logged (contention on main/'s own .git
during a plain git stash push, not inside a build worktree's commit)
— worth a fresh harvest id if it recurs; didn't promote off one
sighting.
Queue is empty: runner status shows every slice BUILT, no
crashed or in-flight entries; every spec file on disk has a matching
ledger record. Nothing carried forward from this session's original
ask.
Uncommitted, expected: same state/runner/{costs,harvest-ideas, rework}.jsonl routine churn.
landed
Started from Sebastian asking for a sanity check on why a harvest run
passed through 289-291 of ~305 open ideas untouched. Measured directly
rather than guessing: build_harvest_prompt was dumping every still-
open idea's full raw event history into a single one-shot dispatch,
unbounded — 510,176 chars / ~127,544 tokens for 305 ideas / 1044 events.
No model can meaningfully reason about that in one pass. Root cause:
_prune_stale_ideas only drops an idea once it's fully closed; every
open idea keeps its entire history forever.
Also found the pre-existing harvest idea that had already diagnosed this
(harvest-digest-unbounded-and-unaggregated) was marked built
2026-08-20, but re-verifying against current code showed only the
costs/rework aggregation half had actually landed — the ideas-history
half was never fixed. Another instance of ledger status not matching
code reality; see harvest-ledger-goes-stale memory, now generalized
past just promoted-when-fixed to built-when-only-half-fixed too.
Specced and landed both fixes:
harvest-digest-compression(d839d0e): each open idea becomes a rollup (seen_count,first_seen,last_seen,latest_event,eligible) instead of full history; ranked byseen_countdesc, ties by recency; top 50 get full rollup detail, everyone else drops to aremaining_ideas: {id: seen_count}tail. Verified live before/after: ~127,544 tokens -> ~9,899 tokens, ~13x reduction.harvest-maybe-stale-verification(ee58b90): newMAYBE_STALE: <id> | <reason>wire format — when the model suspects but can't confirm from digest text alone that an idea is already fixed, it flags it instead of guessing. Each flagged id gets one small follow-up dispatch (same model, same Read/Grep/Bash access the harvest dispatch already had) that must answerCONFIRMED: <proof>orNOT_CONFIRMED: <why>before anything closes as a realOUTCOME. Motivated by two falseOUTCOMEclaims caught and hand-corrected the same day this session started (uv-project-flag-wrong-venv-in-worktree,check-repo-hygiene-bsd-date-and-suffix-regex).
New harvest idea logged: harvest-clustering-groups-unverified-and- already-fixed-ideas — the model's clustering instruction groups
*-friction/*-efficiency/*-refactor ideas by keyword/theme with no
check that the grouped ids share a real root cause or that the
underlying fix hasn't already shipped. The two false-OUTCOME catches
above are both instances of this.
Tool-harness anomaly, one sighting: a runner run build was hard
SIGTERM-killed by the Bash tool's own harness at exactly the 2:00 mark
instead of auto-backgrounding like every other long invocation this
session (exit 143) — left a stale lock + state.json entry
(checkpoints: [], nothing lost). Recovered via the documented
CRASHED procedure (edit state.json, remove the lock, remove the
worktree/branch, retry). Logged as a fresh sighting of
runner-auto-retry-on-stall-build-crashes-silently. Working
around it going forward: for a long runner run, background it
explicitly (& + poll the real PID from separate short Bash calls, or
a plain until ! ps -p <pid>; do sleep 5; done loop) rather than
trusting the tool's own auto-backgrounding.
Housekeeping at close: committed one batch of harvest-ideas.jsonl
lines that belonged to a concurrent session's own 2026-08-30-codex- devcontainer-cli spec work (cold-read/draft findings, plus a new idea
spec-run-draft-path-not-normalized-to-repo-root) — Sebastian asked for
this explicitly (fb4edd0), since per CLAUDE.md I don't stage another
session's uncommitted work on my own judgment.
Push left undone, on purpose: git push failed with "Host key
verification failed" — ~/.ssh/known_hosts is empty in this
devcontainer, so SSH won't verify GitHub's host key. Didn't work around
it (no ssh-keyscan blind-trust, no StrictHostKeyChecking=no).
Sebastian confirmed pushing is his to do, not something to hand back to
Claude to fix. main is 44 commits ahead of origin/main, unpushed at
session end.
Second worktree, not abandoned: 2026-08-30-codex-devcontainer-cli
sits at the same commit as main with its spec approved but not yet
built — a concurrent session's pending work, left alone.