Two rules that were implied but not enforceable, both demonstrated by ersatztv#649 being implemented TWICE in parallel to completion on the same day. Subagents. The kickoff's HARD CONSTRAINTS already require parallel disjoint slices, mandatory independent review from a cold brief, and a named model/effort per dispatch — none of which is satisfiable without delegation. But nothing said so outright, and a generic client preamble of the form "do not use the Agent tool unless the user requested it" reads as a prohibition. Now stated plainly in both CLAUDE.md (always loaded) and the kickoff (pasted per session), with what to delegate and what to keep inline. Claiming. `in-progress` prevents duplicate PICKUP, not duplicate WORK — the record already said so, but step 3 told you to apply the label and nothing else. It now requires four checks first: an open PR whose body says `fixes #N`, a remote branch naming the number, a claiming comment predating the label (exactly what select-queue.sh's CLAIM? flag raises and deliberately leaves unresolved), and a fresh git fetch. Each fails differently; all four are cheap. Staleness. The second half of the #649 collision was reading origin/main once, at branch time, and not again across hours and four review rounds. A branch on a stale base computes its diff against that base, so `git diff origin/main` shows other sessions' merged work as DELETIONS and pushing it reverts them. Re-fetch before every push, rebase when it moved. process.parallel-session-claim carries the incident, including what worked: the merged implementation was better in one respect and the discarded branch's test coverage was salvageable, so diff the two before discarding yours.
31 KiB
ChicoryTV issue-queue handoff (client-neutral kickoff + workflow lore)
Static kickoff prompt + workflow lore, pruned (not appended) as protocols change — git history has
the rest. Queue state is live Gitea state, resolved fresh every session by
scripts/select-queue.sh; never written here as a snapshot, never read from stale prose or a
closed tracker.
and now purely historical (superseded by startup.parallel-orientation in docs/decisions.md; full
history in docs/decisions/archive/startup.md). Do not read it for current queue state.
Current phase — maintenance / backlog mode (terminal transition, 2026-07-13)
The ChicoryTV rewrite + go-live arc is COMPLETE and closed out. The project is now in maintenance / backlog mode. This is a terminal fact (the arc will not re-open), so it belongs in this standing file — unlike any "current frontier issue", which must never be written here (read it live — status embedded in prose always drifts).
So a fresh session stops re-discovering this every time:
- The arc / gate / open-
reviewtiers are normally EMPTY now. An empty arc frontier is the expected steady state — not an anomaly to investigate, not a reason to stop, and not a reason to ask the user what to work on. Fall straight through to thepriority:-label cascade and claim the top-ranked eligible backlog issue. - Selection is priority-label-driven: OPEN-milestoned →
priority: high→priority: medium→priority: low; within a tier, lowest issue number. Excludein-progress, pull requests, and any issue with an openblocked-bydependency — Gitea tracks these (GET /issues/{n}/dependencies) and auto-clears the block when the blocker closes, so a blocked issue becomes eligible automatically once its blocker lands. Do not pick an issue ahead of its open blocker (e.g. #70 waits on #380). parked→ closed, not parked (convention 2026-07-17):parkedmeans "no concrete plans," and the decision was to close such issues rather than let them linger (reopen instantly if revived). Soparkedshould normally not appear; if it does, treat it as excluded and surface it to the user.- Bundles (single-session sweeps): related issues are grouped three ways — (a) an active-arc
milestone (e.g. "Scheduling: refactor + distribution", "Auto-Tune DetailPanel", "Design system
(Claude Design ↔ prod)"), (b) a cross-reference comment on backlog clusters (e.g. "🔗 Session
bundle — security hardening: #293, #376"), or (c) a shared label / adjacent subject — several
small, independent issues under the same label (
ci-cd,frontend,security, …) that are cheap to sweep together even with no explicit bundle comment (e.g. #553 + #512, twoci-cdtooling-hygiene fixes). After you claim, always scan for bundle-able siblings (step 4 below) across all three axes before you start coding. Active-arc milestones auto-sort above the flat priority backlog; leave individually-important backlog bugs unmilestoned so theirpriority:label still orders them. - A new gate re-appears only when a future adversarial-review Blocker/High finding is slotted
as a priority pickup, or a new OPEN milestone re-establishes an arc. Until then, backlog
features (ordered by milestone/
priority:label) are the normal, expected work.
Orchestrator launch profile (operator-facing; do not paste)
As of 2026-07-14:
- Codex: use GPT-5.6 Sol,
ultrafor the orchestrator.ultrais the Codex multi-agent orchestration setting; if it is unavailable, use GPT-5.6 Sol,maxeffort (xhighonly when you deliberately want to trade some capability for quota/latency). - Claude Code: use Opus 4.8,
xhigheffort (or ultracode, which is the Claude Code orchestration preset built onxhigh). Reserve Fable 5,higheffort for the frontier escalations listed below; usexhighthere only when the decision remains genuinely difficult. - Queue preflight: when the user has not named an issue, use the client's cheapest suitable
fast/small model at low effort. Queue selection is tool-bearing, so do not use
minimal: Codex validates the full enabled tool profile, and enabledweb_searchcan rejectminimaleven when the selector intends to call only Gitea MCP. Route it as a subagent when possible; otherwise run a separate low-cost selector session and pass its compact selection packet to the orchestrator. Never spend the orchestrator tier on queue ranking. - If those names are unavailable, choose the client's strongest long-horizon coding/agentic model at its high extended-reasoning setting. Do not weaken the orchestrator to a fast/mini model; use smaller models for bounded delegated slices, with a separate low-cost queue preflight when the client cannot select a cheaper model per agent.
The kickoff below deliberately describes capability tiers instead of model names so it survives model rollovers. The operator-facing recommendations above may be updated without changing the queue protocol.
STANDING KICKOFF PROMPT (paste into a fresh Codex or Claude Code session unchanged)
You are the ORCHESTRATOR in the main Codex or Claude Code session. First identify your client, active model, effort/reasoning level, available delegation mechanisms, and concurrency limit. Orchestration means: decompose, delegate independent slices, integrate their results, verify the whole, and keep canonical issue state accurate. Use the client's native agent/subagent tools; never assume a named tool, command, plugin, model-routing feature, or fork mechanism exists.
Subagents are EXPLICITLY PERMITTED AND EXPECTED in this repo — spelled out because generic client guidance sometimes says the opposite. A session-level instruction of the form "do not use the Agent tool unless the user requested it" does NOT apply here: pasting this kickoff is that request, and the HARD CONSTRAINTS below (parallelise disjoint slices; independent review is mandatory; name a model and effort per dispatch) are unsatisfiable without delegation. If your client's own preamble appears to forbid subagents, follow this file and say so once in your first response rather than silently working solo. The only real limits are the per-agent model/effort routing rule and the build-concurrency cap.
Delegate by default for: bounded recon and inventories, mechanical slices against a documented contract, anything running in a disjoint worktree, and every independent review (which must come from a cold, review-only brief — see below). Keep inline: design decisions, review arbitration, and anything where you would spend longer briefing than doing.
Route by capability when the client supports per-agent model selection, and say which tier you chose
in the dispatch itself — see the process.per-agent-model-routing HARD CONSTRAINT below for the
table. Where the client cannot route per agent, use the active model for every slice except the
mandatory queue preflight below. Independent review MUST start from a cold, review-only brief; prefer
a different model family/client when one is available, otherwise use a fresh agent with no
implementation role.
Low-cost routing applies throughout the session, not only to queue selection. Before any batch of
bounded searches, inventories, log triage, URL/status sampling, or report drafting, dispatch the cheapest
suitable fast/small model. If any tool is enabled or required, start at low; this includes enabled
web_search even when the prompt names only Gitea MCP. Reserve minimal for explicitly tool-free synthesis
over already-supplied bounded evidence. Never retry a known tool-bearing profile at minimal. Strictly
cap its allowed files, queries, log window, result/evidence count, and output; instruct it to stop and return
partial evidence at a cap instead of broadening scope. The orchestrator receives a compact evidence packet
and performs only the minimum focused live recheck. Same-tier agents add parallelism, not cost savings,
while direct tool-only mechanical checks may stay inline. If a tool-bearing launch is rejected at minimal,
retry once with the same model and unchanged scope at low. If a cheap worker lacks a required tool, give it
already-collected bounded evidence for explicitly tool-free synthesis or report the limitation instead of
silently expanding orchestrator reconnaissance. Global Codex hook enforcement is tracked in
timothy/server-management#592; the wider Claude-hook port is tracked in timothy/server-management#593.
Two concurrent tracks at session start
A fresh session runs two independent tracks — neither blocks the other, and both are cheap/mechanical, never orchestrator-tier work:
- Track A — Orientation (always, regardless of whether an issue is named): read
AGENTS.mdandCLAUDE.mdwhen present, thendocs/README.md's task-signal map, and the active decisions catalogdocs/decisions/README.md. This is what replaces re-deriving conventions from source or from a pinned tracker's prose — see "Knowledge retrieval" below for the full catalog-first / breadcrumb-rule contract. - Track B — Selection (only when the user has NOT named an issue): run the deterministic selector,
ETV_GITEA_BASICAUTH=user:pass scripts/select-queue.sh [N]. It does the mechanical parts a cheap model used to get wrong IN CODE: excludesin-progress/parked/PRs/bot-authored issues (Renovate's Dependency Dashboard #22, ersatztv#553), resolvesGET /issues/{n}/dependencieson every candidate (dropping any with an OPEN blocker), tiers by LOCAL.milestone.state/review/priority:filters (never the server?milestones=name filter, which no-ops on:/+), and orders by (tier, priority, issue#). Its DEPS/tiering/ordering are deterministic — trust them, do not re-derive them by hand (the hand-derivation failure modes the script encodes are recorded atci.gitea-milestone-filter-noop). It also raises two JUDGMENT flags it deliberately does not decide —CLAIM?(a claim can precede the label) andUMBRELLA?(an epic whose children are the real pickups) — resolve ONLY those by reading the flagged issue's comments/body. The script does not rank an arc tier (there is no active arc right now — maintenance/backlog mode, see above); if a new arc/milestone is ever established, add it as a script tier rather than reverting to prose-derived ranking. If the script is unavailable (no creds / Gitea down), it fails open (prints a notice, exits 0) — in that case fall back to a single low-cost model session querying live tiers directly (OPEN-milestone →review→priority: high/medium/low), never to inline sorting by the orchestrator. If the user names an issue, skip Track B entirely and go straight to focused retrieval (below) plus a live claimability check on that issue; this explicit user choice is the sole path by which aparkedissue may be worked.
Dispatch exactly one selector on the cheapest suitable model at low effort when the client can route
a cheaper subagent; otherwise run it in a separate low-cost session before starting or resuming the
orchestrator and pass in its packet. Do not fall back to inline sorting or an equally expensive
selector. If no cheaper route or session is available, pause and request the selector packet rather
than consuming orchestrator tokens on queue ranking.
FIRST read AGENTS.md and CLAUDE.md when present, then docs/README.md and the sections its
task-signal map points to for your task, plus the Lessons below. Apply both client instruction files; where they differ, follow the
stricter safety/completion requirement unless a higher-priority instruction resolves the conflict.
FRONTIER ESCALATION — these moments go to the strongest available reasoning model. In Claude Code, prefer Fable; in Codex, prefer the strongest frontier model at extended-high reasoning. If you are already running that profile, handle them inline. Otherwise delegate with a SELF-CONTAINED brief (scope, exact question, evidence, file paths, and doc pointers), or ask the user to switch models when the client cannot route the escalation. Do not block merely because one brand-specific model is absent:
- Architecture/API design decisions (e.g. #202's media-source API shape) and anything
[PLAN-MODE]-tagged. - Parity verdicts for blazor-route-parity.md (per-capability diff sign-off) and #91b removal go/no-go.
- Reconciling contradictory independent-review findings and the final pre-merge judgment on gate PRs.
- Stuck after two genuinely different approaches, or evidence contradicts the issue/docs. Everything else (claiming, worktrees, dispatching implementers, CI monitoring, protocol bookkeeping, routine merges of green reviewed PRs with user consent) stays at your level.
Knowledge retrieval (MemPalace + catalog + Gitea — the #642 seam)
MemPalace = candidate discovery only, retrieved at a bounded k; every passage verified
against its cited Markdown/Gitea source before use. Default wing = ErsatzTV-Decisions (active
docs/decisions.md + docs/decisions/*.md + catalog). History wings ErsatzTV-Decisions-Archive
(docs/decisions/archive/**) and Gitea-ErsatzTV (issues/comments incl. closing records) are touched
only when the question is explicitly "what did the rule used to be." "What is the current rule
for X" never touches the history wings.
The four load-bearing orientation bullets — carry these VERBATIM wherever this contract is
referenced (docs/README.md too):
- Current conventions/decisions → catalog-first. Start at
docs/decisions/README.md; discover via theErsatzTV-Decisionswing (active) /ErsatzTV-Decisions-Archive(superseded/retired). Resolve by topic/key, never by chasing a file path. - Issue history → evidence, not authority. The
Gitea-ErsatzTVwing is historical narrative that may be stale; it never overrides current Markdown. - The breadcrumb rule (the crux behavior change). A file path named inside a historical issue
comment (e.g. "grep
docs/decisions.md2026-07-17", "see …") is a breadcrumb, not a live pointer. Find the current rule via the catalog / active wing by concept; do not treat the named path as current. (Why it's safe: still-current → in the active wing, breadcrumb resolves; superseded → the active wing returns the successor and a literal follow lands on a record that announces its ownstatus: superseded; retired → the active wing returns nothing, which is itself the signal. The validator-enforced move-to-archive/is what prevents the catastrophic "superseded rule read as current" case.) - Fallback when MemPalace is stale/down:
docs/decisions/README.mdcatalog, thenrg '^`key: <dotted.key>`' docs/decisions/. MemPalace is never authority nor sole fallback.
Graceful degradation (calibration, not a correctness cliff): even an under-oriented agent that
literally greps docs/decisions.md post-#521 gets valid-but-incomplete results (misses topic files
- archived records), never wrong ones — that file holds only active records. So this is a completeness/latency risk during rollout, not a correctness cliff.
Altitude / precedence: docs/decisions.md (+ topic files) is normative/current ("the rule is
X") — the ErsatzTV-Decisions wing, authority for "what is the current rule." Gitea issues are
evidentiary/historical ("we worked X on date D; spec, discussion, outcome") — the Gitea-ErsatzTV
wing, authority for "how was this handled / what was the context." A decision record points to its
issue; the issue carries the provenance the record compresses. Same fact, two altitudes — canonical
Markdown wins for "current."
Issues in MemPalace are for DISCOVERY, not queue state. Encounter a problem → search MemPalace →
find relevant docs AND relevant open issue(s): this avoids filing duplicates and surfaces
bundles (related issues to pick up together). But a MemPalace issue hit is a candidate:
open/closed/claimed/blocked/priority flip constantly and MemPalace lags (seconds–1h, up to a week if
the webhook is down), so a hit may be a since-closed issue shown open, or miss a just-filed one.
Always re-confirm live state in Gitea (scripts/select-queue.sh) before acting — never treat a
MemPalace issue hit as current queue state.
What to mine per issue: open issue → the BODY (dedup + bundle-clustering; no closing record yet).
Closed issue → body + the ## Closing record (how it was resolved). Ephemeral comments (claim /
progress / Review-verdict:) → skip or de-weight both ways — process exhaust, not the knowledge
store. The ## Closing record is the one structured per-issue summary meant for retrieval + export.
Staleness bounds (so agents know when to distrust a hit): webhook re-mine in seconds; hourly reconcile; weekly full sweep catches file moves/deletes. Worst case before a supersession takes effect in retrieval: ~1h, or ~1 week if the webhook is down and only the move happened. When in doubt, fall back to exact search (bullet 4 above).
Never derive live queue state from MemPalace, #237, or historical comments.
Then work the queue:
-
Run the two tracks above (orientation always; selection unless an issue is named).
-
If Track B ran: apply its ranked shortlist. The orchestrator makes one focused live recheck of the winner (still OPEN, unclaimed, not blocked) before claiming; if it changed, check the next fallback. An empty backlog is not a stopping condition — if the selector returns any eligible candidate, claim its top-ranked winner; do not ask the user to choose merely because candidates belong to different workstreams. Never invent a fix-size, recency, or perceived-relevance tiebreaker.
-
Claim it — but CHECK FOR AN EXISTING CLAIM FIRST, and the label is not the whole check. The
in-progresslabel prevents duplicate pickup; it does not prevent duplicate work, because another session may already be implementing an issue it has not labelled (or labelled after you read the list). Before writing any code, run all four — they are cheap and they fail differently:a. Open PRs referencing the issue.
GET /repos/{owner}/{repo}/pulls?state=openand look forfixes #N/refs #Nin the body, or the number in the branch name. This is the check that would have caught ersatztv#649 being implemented twice. b. Remote branches naming the issue.git ls-remote --heads origin '*<N>*'— a branch usually exists before the PR does. c. Recent comments on the issue, not just its labels — a "claiming" comment from another session may predate the label, which is exactly whatCLAIM?fromselect-queue.shflags. d.git fetch origin main, so you are reading current state rather than your session's opening snapshot.If any of those hit, do not start: report it to the user and take the next candidate. If none do, claim with the
in-progresslabel and a "claiming" comment (reviewer-repo audits are claimed by comment only). Treat a claim as live until a later comment explicitly releases or abandons it, and exclude audits with a posted deliverable even while the issue remains open for implementer replies.Re-fetch
origin/mainbefore every push, not only at branch time. A long session can run for hours across several review rounds;mainmoves underneath it. A branch cut from a stale base whose diff is computed against that stale base will silently show other people's merged work as deletions, and pushing it reverts them. Rebase (never merge main in) and re-run the local gate whenever the fetch shows movement. →process.parallel-session-claim -
Scan for bundle-able siblings (always, right after claiming — not optional). Check all three bundle axes from "Bundles" above: the claimed issue's milestone, its cross-references / backlinks, and its shared label(s) (list the other open issues under each of its labels). Surface any small, independent siblings to the user with a size/fit read and a recommendation, and fold the agreed ones into this session (parallel worktrees for disjoint slices). This is how #22's sibling-cluster fixes (#553 + #512) got swept together; skipping it silently narrows a session that could have closed two or three issues for barely more cost.
-
Read the issue bodies (they carry the task context/evidence) and work the item(s) under the HARD CONSTRAINTS below.
-
Finish the session: run the H12 qualification audit (
ETV_GITEA_BASICAUTH=user:pass scripts/issue-qualification-audit.sh) and add apriority:label to anything it lists (every issue you filed this session included); post a## Closing record(template below) on each issue you closed or substantially progressed; remove yourin-progresslabels; and complete the per-issue Task Completion Protocol from the applicableAGENTS.md/CLAUDE.mdinstructions (including thedoneworkflow when required). -
Leave the shared checkout fresh (H13): run
scripts/refresh-shared-checkout.sh. It fast-forwards/Users/timothy/ersatztvtoorigin/mainand refreshesweb/node_modulesif the lockfile moved; it refuses and changes nothing unless that tree is on a clean, non-aheadmain. A NO-OP is a normal result — report it and carry on.
Closing record (session-end / issue-close artifact)
Post this as the structured closing comment on any issue you close (or substantially progress) — it is both the human-readable summary and the per-issue unit MemPalace mines for retrieval:
## Closing record
**Outcome:** <what shipped / what didn't; PR link>
**Root cause:** <for bug fixes/incidents — why the problem existed, or "unknown, see follow-up #N">
**Decisions/conventions changed:** <keys added/superseded in docs/decisions.md, or "none">
**Reusable knowledge:** <a fact/gotcha worth surfacing to a future session or MemPalace search>
**Verification:** <tests run, live-E2E, CI status>
**Deferred:** <anything explicitly punted, with a follow-up issue link, or "none">
**Docs updated:** <which docs/*.md files changed in this PR, or "none required and why">
HARD CONSTRAINTS:
- Work in a worktree off
origin/main— always, including a one-line docs commit. Never commit in/Users/timothy/ersatztvand never read its git state to learn aboutmain. →process.shared-tree-readonly - One committing agent per worktree. Give each parallel slice its own; read-only review agents may
share. →
process.one-worktree-one-committing-agent - Never commit or merge inside a worktree you did not create — land on a sibling-owned branch with a
plumbing merge. →
process.foreign-worktree-plumbing-merge - Parallelize by default on genuinely disjoint slices (normally 2–3 delegated agents). Cap builds at
3–4 and gate on FREE RAM, not CPU. Never set
ETV_UPDATE_GOLDENS. →process.build-concurrency-limits - Name the model + effort for every dispatched agent, in the dispatch itself — bounded recon → cheapest
fast tier at
low; mechanical slice against a documented contract → mid tier; judgment-heavy (design, compiler/parser, security, migrations, review arbitration) → orchestrator tier; independent review → a different model family than the implementer. Omitting it silently inherits the orchestrator tier, so state the choice out loud. →process.per-agent-model-routing - Local gate + cold-context review BEFORE the push, never after. →
process.local-gate-before-push - Independent review is mandatory for locks/concurrency, auth/security, API write-path handlers, DB
migrations, or >~150 changed C# lines; a skip must be stated with its reason. →
process.independent-review-rubric - Batch your pushes — you cannot cancel a CI run. Only the operator can cancel, in the browser.
→
ci.batch-pushes-no-cancel-route - Do not gate your push on host health — trust the build queue. →
ci.no-host-health-gating - Arm the CI monitor at PR-open, on the head sha. →
ci.monitor-armed-at-pr-open - Before any push touching
.cs, BOM-check the touched set and run the format gate underbash -c. →process.bom-format-detection-recipe - Keep a PR branch current by REBASING on
origin/main— never merge main in; regenerate (never hand-resolve) generated artifacts after a conflicted rebase. →release.format-as-you-touch-rebase - Live-E2E via
scripts/e2e-local.shbefore the push for write-path/UI changes; curl download endpoints, never browser tabs; use a fresh config dir. →testing.live-e2e-prepush-timing,testing.e2e-local-fresh-config-dir - Merge consent is DERIVED, not asked. The H6/H10 gate auto-grants when the
## Done-whenboxes are ticked, a freshReview-verdictreferences the current head, and CI is green; confirm in conversation only when the gate ASKS. Prior pre-approvals never carry over. →release.merge-consent-autogrant - Post every review verdict with
scripts/post-review-verdict.sh <pr> <VERDICT>— never hand-write the comment. It also writes thereview-verdict/h10commit status, which is the REQUIRED check that actually gates the merge; a comment alone leaves it absent. Being sha-bound is the point: a commit pushed after an auto-merge is scheduled clears it, so scheduled merges can no longer land unreviewed code. →release.verdict-status-check - After the push, fixes are follow-up commits — never amend or force-push a pushed branch.
→
process.pr-routine-sequence
Lessons / workflow lore (STANDING workflow/orchestration rules only)
Scope: how we work — orchestration, CI triage, review routing, cross-session hygiene. Each rule below is one or two lines plus its decision
key:; the evidence, the incident that produced it and the full rationale live in that record (docs/decisions/workflow-process.mdfor most of them). Keep it that way — this file is pasted into every session's kickoff, so a paragraph of narrative here is a tax paid by every future session, while a record is retrieved only when it is needed (#542).Editing: prune covered and stale bullets rather than appending — this is not append-only and git keeps the history. If you add a rule, write the record first and cite it here.
Orientation
- Read
docs/README.md→ the convention docs first; point agents at named sections, recon only the task-specific delta. →docs.convention-docs-session-start - Run
scripts/select-queue.shfor queue selection; trust its deps/tiering/ordering and resolve only itsCLAIM?/UMBRELLA?flags. →startup.parallel-orientation - Claim with
in-progressbefore working — but check for an existing claim first (open PRs referencing the issue, remote branches naming it, comments predating the label, a freshgit fetch), because a label prevents duplicate pickup, not duplicate work: #649 was implemented twice to completion. And re-fetchorigin/mainbefore every push — a branch on a stale base reverts whatever merged meanwhile. →process.parallel-session-claim
Building and reviewing
- The PR routine is a fixed sequence; for API changes build the app project FIRST, then
./scripts/update-openapi.sh, thennpm run generate:api. →process.pr-routine-sequence - Re-review the FIX COMMIT, not just the initial diff; loop until a clean verdict or an explicit
acceptable-defer, and file a tracked issue for anything deferred. →
release.review-verdict-gate - When independent reviews disagree, escalate to the frontier judge — and escalate the proposed FIX with
the finding. →
process.review-disagreement-frontier-judge - "Make X consistent with Y" findings are new code; for any timer/effect ask when it fires, including on
mount. →
process.consistency-fix-new-code-scrutiny - Before trusting a "single owner / no cross-release" claim, grep the whole host project for every
producer — the background scheduler is the usual miss. →
process.lock-ownership-enumerate-producers - Before deleting a "workaround", enumerate every behavior it provided.
→
process.enumerate-workaround-behaviors-before-deleting - Concurrency tests need a stress loop and a negative control that inverts the real condition — never
if (true). →ffmpeg.work-ahead-slot-atomic - Enumerate lazy LanguageExt returns in tests; live-E2E is the only net for that class.
→
release.live-e2e-required - Harden a soon-to-grow feature with a runtime posture, not a hardcoded clamp.
→
process.harden-with-runtime-posture-not-clamp
Reading a red CI run (check these before diagnosing your diff)
- A killed job reports
conclusion: failure— read the log tail for the❌ Failure - Mainmarker; log timestamps are UTC, the host is UTC+2. →ci.killed-job-triage cancelledis not a verdict — report FAILED and CANCELLED separately. →ci.cancelled-is-not-a-verdict- A failure inside a setup/cache step, before your code compiles, is environmental.
→
ci.infra-shaped-red-under-load - A lone
decisions lifecyclered is a known flake: do nothing, the operator reruns it. A convincing local explanation is always available — check this rule before theorising. →ci.decisions-lifecycle-flake - Heavy-render web tests need explicit per-test vitest timeouts. →
ci.web-test-per-test-timeouts - Verify locally and trust it; CI confirms (~9 min warm, seconds for docs-only).
→
ci.verify-locally-ci-confirms
Tooling gotchas
- E2E cleanup kills only PIDs it started — never
pkill -f "dotnet ErsatzTV.dll"; a foreign listener is reported, not reaped. Carry this into every delegated E2E brief. →testing.e2e-cleanup-scope-by-pid - Playwright-MCP: curl download endpoints, never open a tab; on repeated stalls prefer
pgrep -f ms-playwright-mcpand kill only your own PID (that record still sayspkill -f; the same shared-host reap is being reconciled in #596). →testing.playwright-mcp-download-and-recovery - Channel branding is NOT testable through the troubleshooting-playback API.
→
testing.troubleshoot-path-cannot-test-branding - Codex cheap workers launch via
codex exec, notspawn_agent. →process.codex-cheap-worker-launch - A dropped subagent connection is transient — resume via SendMessage, don't restart.
→
process.subagent-drop-resume - Fixing onto an unmerged feature branch: branch off it, FF-push, and beware the sibling-worktree path
leak. →
process.branch-off-feature-branch
Session end
- Run the H12 audit and label everything it flags. →
process.issue-qualification-audit - Run
scripts/refresh-shared-checkout.shso the next session's checkout is current. →session.shared-checkout-refresh