Files
ersatztv/docs/decisions/workflow-process.md
T
fb55929711
Build ErsatzTV Image / CI toolchain image resolves (push) Successful in 7s
Build ErsatzTV Image / Delimiter ban (release path) (push) Successful in 23s
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 9m5s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (push) Successful in 6m24s
Build ErsatzTV Image / Functional E2E (curl + UI contracts) (push) Successful in 6m10s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (push) Skipped
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (push) Skipped
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 4m17s
fix(869,893): re-establish the 1.25.4-dated CI claims on 1.27.1, and settle the page_statuses asymmetry from source (#905)
Finishes the 1.25.4-dated CI claim sweep #747 deliberately left incomplete (#869), and answers #893 from the Gitea v1.27.1 source instead of inferring it from a header. Docs and comments only - zero non-comment changes in scripts/ and .gitea/.

Population derived with `git ls-files`, not from the issue's item list: 17 files, 43 occurrences of `1.25.4`, against the 4 items #869 named.

Re-established on 1.27.1: the `creator`-attribution claim the H10 allow-list rests on (4 merged heads, both endpoints); the scope enum (no `status` scope); the `reqRepoWriter(unit.TypeCode)` gate; the `write:package` 403 (live probe with a read control 200 and a write control 201, throwaway repo, artifacts deleted); the absence of any REST cancel route (from source, which a 404 alone cannot establish); and `pull_request`/`pull_request_target` definition resolution.

#893: `/statuses/{sha}` does NOT drop rows after pagination. `getCommitStatuses` appends unconditionally and its only filter is a SQL WHERE in the same query as the LIMIT/OFFSET, so an empty page really is the end, `page_statuses` terminating on its first empty page is safe, and the asymmetry with `count_pr_mutations` is correct - recorded with its reason and a date so it is not tidied away.

Corrected rather than re-dated: the `--depth=1` no-merge-base claim was filed against the wrong axis (a git property, re-probed on git 2.55.0), and `enable_bypass_allowlist` postdating 1.25.4 had an issue body as its only provenance.

Five cold review rounds plus a cross-family Codex pass. They caught a wrong MECHANISM for `creator: null` (it is `CreatorID == -2`, not `== 0`), an evidence count that straddled the upgrade, and a reason for not re-probing MCP `cancel_run` that was invented - all fixed, final verdict CLEAN.

fixes #869
fixes #893

Decisions-Edit: yes
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Whe75djeAEuZpdNk6KU7No
Co-authored-by: Timothy <timothy@noreply.gitea.tblindustries.be>
2026-09-02 20:20:43 +00:00

9.5 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: no agent-side cancel route; REST absence confirmed in the v1.27.1 router source, web UI still 1.25.4-dated (#542, #869) — [`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)