168 records -> docs/decisions/records/<area>/<topic>.md (163 active, 23 dirs) and docs/decisions/archive/<area>/<topic>.md (5 archived). The filename IS the key, so one-active-record-per-key becomes a filesystem property rather than a validator check, and supersession becomes a `git mv`. WHY: the monolith was a concurrency problem before an aesthetic one. A 3,900-line append target made parallel sessions collide -- PR #605 and PR #614 both hit append-vs-append conflicts during routine rebases, and hand-resolving those inside the corpus is exactly the operation the rationale-rewrite guard exists to police. HOW IT IS VERIFIED: a ~170-file diff cannot be meaningfully read, so correctness does not rest on reading it. The parser was taught BOTH formats first, so the body-diff guard parses the old form at the merge-base and the new form at head -- the migration validates itself, no bypass. The proof is a field-level equivalence harness: 168 records before and after, zero lost, zero gained, zero field mismatches, zero rationale bodies differing. Reviewers should scrutinise the harness; it is the actual evidence. What measuring caught that reading would not have: - ~500 lines sit OUTSIDE any record -- decisions.md's lifecycle schema and each topic file's preamble, mostly the only copy. Source files are kept and stripped, never deleted. They also cannot be filed per-area: topic files hold several areas and 4 of 23 areas span several files. - Archive discovery was a non-recursive glob; after the split it found ZERO archived records, surfacing as four bogus "supersedes points to unknown key" errors rather than an obvious failure. - ~32 live docs point into the corpus BY DATE, which the split dangles. Each stripped file now ends with a generated "Records formerly in this file" index, which also rescues the identical breadcrumbs in old issue comments. - decisions.md's "In this file:" list was 97 same-file anchor bullets that the split makes WRONG, not merely stale. Dropped; the generated index replaces them with links that resolve. The equivalence harness now runs against a checked-in FIXTURE, not the live corpus. The earlier version migrated the real tree, which made it a one-shot: the moment the migration landed there was nothing left to move and the tests failed for reasons unrelated to the code. A fixture keeps them testing the SCRIPT rather than the repo's current state. Keys preserved verbatim, warts included: `sched` (12) and `scheduling` (1) remain two directories for one concept. Renaming a key is not a move -- it changes identity, breaks the equivalence proof, and invalidates MemPalace's per-key drawers. Taxonomy normalisation is separate work. refs #610
9.4 KiB
Session-workflow, CI-triage & review-process decisions (#542)
How we work — orchestration, CI-run triage, review routing, worktree and parallel-session hygiene, and the tooling gotchas that have cost real sessions. Distinct from the engineering "why the code does X" records: nothing here decides product behaviour.
These records were extracted from docs/handoffs/chicorytv-issue-queue.md in #542. That doc had
become the only copy of most of this knowledge — an inventory found ~30 of ~38 lore bullets had
no decision record at all — so it was neither retrievable via the catalog nor safe from a careless
prune. The rules are now records; the handoff doc keeps the instruction and points here for the why.
Related: release-ci-governance.md (merge/release gates and hooks), startup.parallel-orientation
(session-start tracks), session.shared-checkout-refresh (session-end shared-tree refresh).
— it is noise, not a cause. Grep for the failure marker, not for the word "error".
next session debugging a phantom. Split the two counts explicitly.
runners, so high load is not always CI's own doing.
one of the 4 runner slots while doing it, which is a real cost with several parallel sessions.
conflating them leads to load-watching behaviour that this record forbids.
before theorising about a cause.
rather than diagnosis.
suite in exchange for fixing one known-slow case.
local verdict; it is not the first line of defence.
the merge record can be re-read later.
cheap fast private check is skipped in favour of the expensive slow shared one.
real" and let a starving fix ship. Send the fix approach up with the finding.
timer that fires once more than the author pictured.
failure mode a second, differently-scoped reviewer catches.
still holds.
refresh script that mitigates it. This record covers the git-state failure mode only.
#216.
route mutates only the ref, so the merge lands without disturbing the owner's tree.
tests". A git status in the new worktree before the first build catches this in one command.
hides exactly the change the golden net exists to surface.
not the agent.
the review label and milestone. A milestone is not required — backlog is unmilestoned.
the single most contended file across parallel sessions.
claimed local admin makes its Setup-gate spec unreachable.
not the overlay inputs. Route branding verification through a real channel playout instead.
retrying the same call.
names, then update this record once verified.
settle on and the browser tool hangs.
and write the structured close comments the applicable `AGENTS.md` / `CLAUDE.md` protocol requires.
feature's issue for its end-state *before* choosing between a switch and a wall.
commit `f93458c7`, where it was dropped by a later prune rather than disproved.)
rule, which is the habit being built.
inventing one) and an explicit statement of who owns the printed PID.
## Records formerly in this file
Each record below moved to its own file under `records/` (ersatztv#610); the rationale is
unchanged. Resolve by **key** — that is the stable identity. A date-based pointer from
another doc or an old issue comment should land here and then follow the link.
- 2026-07-21 — "Make X consistent with Y" review findings are new code and get new-code scrutiny (#542) — [`process.consistency-fix-new-code-scrutiny`](records/process/consistency-fix-new-code-scrutiny.md)
- 2026-07-21 — A dropped subagent connection is transient; resume, don't restart (#542) — [`process.subagent-drop-resume`](records/process/subagent-drop-resume.md)
- 2026-07-21 — A killed CI job reports `conclusion: failure`; read the log tail before diagnosing the diff (#542) — [`ci.killed-job-triage`](records/ci/killed-job-triage.md)
- 2026-07-21 — A lock/channel "no cross-release" verdict must enumerate every producer via grep (#542) — [`process.lock-ownership-enumerate-producers`](records/process/lock-ownership-enumerate-producers.md)
- 2026-07-21 — A lone `decisions lifecycle` red is a known infra flake: do nothing (#542) — [`ci.decisions-lifecycle-flake`](records/ci/decisions-lifecycle-flake.md)
- 2026-07-21 — An infra-shaped red under host load is not a code failure (#542) — [`ci.infra-shaped-red-under-load`](records/ci/infra-shaped-red-under-load.md)
- 2026-07-21 — Arm the CI monitor at PR-open, via the commit-status endpoint (#542) — [`ci.monitor-armed-at-pr-open`](records/ci/monitor-armed-at-pr-open.md)
- 2026-07-21 — BOM/format pre-push detection: use the `xxd` loop, and run `dotnet format --include` under `bash -c` (#542) — [`process.bom-format-detection-recipe`](records/process/bom-format-detection-recipe.md)
- 2026-07-21 — Batch your pushes: there is no agent-side cancel route on Gitea 1.25.4 (#542) — [`ci.batch-pushes-no-cancel-route`](records/ci/batch-pushes-no-cancel-route.md)
- 2026-07-21 — Before deleting a "workaround", enumerate every behavior it provided (#542) — [`process.enumerate-workaround-behaviors-before-deleting`](records/process/enumerate-workaround-behaviors-before-deleting.md)
- 2026-07-21 — Bound parallel builds by FREE RAM, never regenerate goldens (#542) — [`process.build-concurrency-limits`](records/process/build-concurrency-limits.md)
- 2026-07-21 — Build and verify locally, then trust it; CI confirms (#542) — [`ci.verify-locally-ci-confirms`](records/ci/verify-locally-ci-confirms.md)
- 2026-07-21 — Channel branding is NOT testable through the troubleshooting-playback API (#542) — [`testing.troubleshoot-path-cannot-test-branding`](records/testing/troubleshoot-path-cannot-test-branding.md)
- 2026-07-21 — Claim with `in-progress` before working; claiming is not collision safety (#542) — [`process.parallel-session-claim`](records/process/parallel-session-claim.md)
- 2026-07-21 — Do not gate or throttle pushes on host health (#542) — [`ci.no-host-health-gating`](records/ci/no-host-health-gating.md)
- 2026-07-21 — Every open issue carries a `priority:` label — run the H12 audit at session end (#542) — [`process.issue-qualification-audit`](records/process/issue-qualification-audit.md)
- 2026-07-21 — Fix onto an unmerged feature branch by branching off it and FF-pushing (#542) — [`process.branch-off-feature-branch`](records/process/branch-off-feature-branch.md)
- 2026-07-21 — Gitea's `?milestones=` issue filter silently no-ops on names containing `:` or `+` (#542) — [`ci.gitea-milestone-filter-noop`](records/ci/gitea-milestone-filter-noop.md)
- 2026-07-21 — Harden a soon-to-grow feature with a runtime posture, not a hardcoded clamp (#542) — [`process.harden-with-runtime-posture-not-clamp`](records/process/harden-with-runtime-posture-not-clamp.md)
- 2026-07-21 — Heavy-render web tests need explicit per-test vitest timeouts on the CI VM (#542) — [`ci.web-test-per-test-timeouts`](records/ci/web-test-per-test-timeouts.md)
- 2026-07-21 — Independent cross-model review is mandatory on risky diffs; a skip is a stated, auditable exemption (#542) — [`process.independent-review-rubric`](records/process/independent-review-rubric.md)
- 2026-07-21 — Launch Codex cheap workers via `codex exec`, not `spawn_agent` (#542) — [`process.codex-cheap-worker-launch`](records/process/codex-cheap-worker-launch.md)
- 2026-07-21 — Live-E2E runs BEFORE the push, and downloads are curled, not browsed (#542) — [`testing.live-e2e-prepush-timing`](records/testing/live-e2e-prepush-timing.md)
- 2026-07-21 — Local build/test gate + cold-context review run BEFORE the push, not after (#542) — [`process.local-gate-before-push`](records/process/local-gate-before-push.md)
- 2026-07-21 — Never commit or merge inside a worktree you did not create (#542) — [`process.foreign-worktree-plumbing-merge`](records/process/foreign-worktree-plumbing-merge.md)
- 2026-07-21 — One worktree, one committing agent (#542) — [`process.one-worktree-one-committing-agent`](records/process/one-worktree-one-committing-agent.md)
- 2026-07-21 — Playwright-MCP: curl download endpoints, never open a tab or `window.open` (#542) — [`testing.playwright-mcp-download-and-recovery`](records/testing/playwright-mcp-download-and-recovery.md)
- 2026-07-21 — Review disagreement on a gate PR escalates to the frontier judge — and the proposed FIX escalates with it (#542) — [`process.review-disagreement-frontier-judge`](records/process/review-disagreement-frontier-judge.md)
- 2026-07-21 — Run `scripts/e2e-local.sh` against a FRESH config dir; a reused one hangs the readiness probe (#542) — [`testing.e2e-local-fresh-config-dir`](records/testing/e2e-local-fresh-config-dir.md)
- 2026-07-21 — The PR routine is a fixed sequence: validate locally, then push, then only follow-up commits (#542) — [`process.pr-routine-sequence`](records/process/pr-routine-sequence.md)
- 2026-07-21 — The shared tree at /Users/timothy/ersatztv is read-only and tells you nothing about `main` (#542) — [`process.shared-tree-readonly`](records/process/shared-tree-readonly.md)
- 2026-07-21 — `cancelled` is not `failure`; a cancelled run is no verdict (#542) — [`ci.cancelled-is-not-a-verdict`](records/ci/cancelled-is-not-a-verdict.md)
- 2026-07-25 — E2E cleanup kills only the PIDs it started; never a `pkill -f` pattern (#586) — [`testing.e2e-cleanup-scope-by-pid`](records/testing/e2e-cleanup-scope-by-pid.md)
- 2026-07-25 — Name the model tier for every dispatched agent; a PreToolUse gate makes the silent default visible (#583) — [`process.per-agent-model-routing`](records/process/per-agent-model-routing.md)