پرش به محتوا

Journal events

مستندات به انگلیسی نوشته شده است. بقیهٔ سایت به فارسی خوانده می‌شود.

Every event kind this console can write, what emits it, and what it means.

Why the file exists. Every event kind the console emits is a row in the table below — a couple of hundred, and viewer/test/debug-index.test.ts pins the table against the code, so the number is never written here to go stale; before this the five doc surfaces between them named 27. A journal is only evidence if a reader can tell what a line means, and "grep the server for the string" is not a document — it is the absence of one.

Two sinks, and the difference matters.

sinkgoes towho reads it
journalthe RUN's journal (runs/<instance>/<slug>/run-<runId>.jsonl)the console's timeline, the phase detail, analysis/timeline.ts, and you
logthe console's own log filean operator debugging the console itself

A log line is about the CONSOLE; a journal line is about the WORK. A few kinds appear in both, and are marked both.

Both tables are machine-checked in BOTH directions by viewer/test/docs-parity.test.ts: every '<phase|run|policy>.<name>' string literal under viewer/server/, every name a log.info/warn/error('…') call writes, and every name handed to the sessions registry's onWarn must appear here, and every row here must appear there. Add an event without a row and the suite goes red; delete an event and leave its row and the suite goes red the other way. That is the only thing that keeps a list this size true. Two rules the widening added (LFC-4): an event name is never composed at runtime — no template literal, no name arriving in a variable — because a composed name is one this file cannot hold (run.park-withdrew reached a journal that way, undocumented); and a retired event keeps its row, with retired <version> in the emitter column instead of a file, so a log written before the upgrade stays readable — the checker skips such a row in the "must be emitted" direction and fails if the name is ever emitted again.

Payload. Every entry carries at, and a phase-scoped one carries phase. The rest is per-event; the emitting file named below is the authority on it.

eventsinkemitted bymeaning
phase.account-switchjournalrunner/runner-control.tsThe lane moved to another Claude account — which one, and what became of the transcript: ported is BYTES MOVED by this switch, findable is whether the resumed session will find its conversation where the paying account looks (a same-directory switch reads ported: false, findable: true, why: 'nothing to carry' — ACT-12), why names the case, cliVersion the CLI whose undocumented layout the port relied on. rekeyed (control-tower phase 78, #92): how many of the run's queued entries moved to the new account with it. by: ladder (control-tower phase 79, #98) is the drive loop's own switch-account rung on a phase left pending at a usage wall, through moveAccount like every other move. at: boundary with policy and source (one-pool phase 7) is the POOL's move at a phase's boundary (poolAtBoundary): the phase's own accounts, a spill back to the kind its policy prefers, or round-robin's or the weighted draw's next account — reason is the verdict's sentence.
phase.admittedjournalanalysis/timeline.ts, runner/runner-control.tsAdmission let the phase through: it holds its lock and its scope, after this long queued. reserved: true (control-tower phase 6) when it boarded on a landing's reservation (AdmitRequest.reserve, or given in place to an entry already queued by Scheduler.reserveQueued): aged at birth, first in the scan, and holding its tokens the moment its scope blocks it — but never preempting a granted lane and never passing a clock. The reservation is spent here. Since control-tower phase 60 (#81, #82) waitedMs is the whole wait across every episode a withdrawal or restart cut it into, and releasedBy names what released it — the last head holder a scan saw (slug, phase, owner, kind, class); absent when it waited for nothing. One line per grant.
phase.dependency-heldjournalrunner/runner-loop.tsA candidate was NOT admitted because a dependency of its own is not done (one-pool phase 23, #288): its handoff put it in stuck or in-progress, a bucket that says nothing about its dependencies, and the board's blocked: line names the unmet ones. {reason: "dependency", unmet, board}; said once per unmet set, and the dependency boards first.
phase.api-retryjournalrunner/runner.tsThe CLI's own retry watchdog fired. category is the CLI's documented error value (one of twelve, never read from error_category), marked inferred only when guessed from text; attempt, maxRetries, retryDelayMs and errorStatus (null when no response arrived) ride beside it. Not work, and deliberately not productive output.
phase.answeredjournalrunner/runner-control.tsThe session's reply to an operator's question, beside the question (TRS-6): question, options (empty — an operator's question is free text), chosen (the reply, bounded), by: session with its sessionId, askedBy, the mark that paired them and ms from asking to answering. Once per question; a repeated or unknown mark writes nothing.
phase.approval-auto-grantedjournalservice.tsA card the console answered itself under a standing auto-grant setting — tool, the suggested rule, the ask rule that actually matched (null when none did), the level that decided (phase, plan, global, default) and approvalId. exception {rule, value, source} when the call was a publishing ask (git push, gh pr create) the plan's permission.destructive row allowed; such a grant is also announced. Since control-tower phase 84 (#112) a row may also answer a push by the BRANCHES it names, inside a compound command with read-only neighbours, whatever auto-grant is set to: the line then carries answeredBy: permission.destructive, exception.why and exception.branch, and level: manifest when auto-grant was off. Since control-tower phase 107 (#205) the row is read for the RUNNING phase (manifestVerdict, runner/manifest-verdict.ts): a command it names for this phase (gh pr create, gh pr merge --squash --delete-branch, …) is answered the same way, and exception.why names the command and the phase. Renamed from phase.tool-auto-granted in 5.0.0.
phase.approval-decidedjournalservice-base.tsA card came down — by a person, another device, the run ending (disarm) or the timeout (TRS-7): approvalId, kind, decision, decidedBy, waitedMs, and recovered: true for a card kept answerable across a restart. The run journal's twin of approval.decided; every ending writes exactly one. Since (control-tower phase 96, #142) also reason — the why the card was decided with, which it always kept and this line dropped. Since turn-by-turn phase 5 also door (the door it was decided through, when the card knows it) and title: a card a person decided is kept as a settled record (turn/settled.ts), so Your turn's Done shows it as Passed or Closed.
phase.approval-expiringjournalservice-base.tsAn approval card is about to run out (control-tower phase 97, #140) — T-15 and T-5 minutes, once each per deadline: approvalId, expiresAt, onTimeout (what its timeout will do) and leadMs. The same moment is announced to the push and the notification inbox; the run journal's twin of approval.expiry-warning.
phase.approval-extendedjournalservice-base.tsA person extended a pending card (control-tower phase 97, #140) — from the card, the inbox or the expiry push: approvalId, at, by, minutes, to (the new deadline), expiresAt, onTimeout, and standsUntil when the extension reached past the hook call's hard limit. The run journal's twin of approval.extended.
phase.approval-reshapedjournalservice.tsA publishing call the plan's permission.destructive row allows in its BARE form, made in a shape the row cannot answer as it stands — a git add/git commit beside the push, a $( … ) that writes, a branch the shell computes, a bare git push, a push inside a substitution, a group, a payload or an unquoted here-doc — refused AT ONCE, with no card (control-tower phase 107, #186) — and, the same way, a manifest-allowed publishing act carrying a command the run's policy would not allow on its own (why names it): tool, rule, why, bareForm (the command to re-run alone), answeredBy: permission.destructive, row {value, source} and the command, bounded. The session is told to re-run bareForm alone and read its result in a separate call. A card an automatic actor settles under the same answer resolves deny by manifest, never timeout, so nothing parks.
phase.approval-granted-oncejournalservice.tsA call a person allowed on a STANDING card — one that outlived its hook call — was let through, once (control-tower phase 97, #140): tool, approvalId. The next such call asks as usual.
phase.approval-raisedjournalservice-base.tsA card went up in front of a person for this run (TRS-7): approvalId, kind (tool, gate, verify), title, expiresAt, and tool, matched and standing where they apply. The run journal's twin of approval.requested — a raise used to be on no run at all.
phase.approval-standingjournalservice-base.tsA card extended past its hook call reached the call's hard limit (control-tower phase 97, #140): the session was told no and the card now STANDS, answerable for a standing card's twelve hours — approvalId, expiresAt, onTimeout, convertedAt. The run parks on it (awaiting-person, the card's id on the halt); a person's Allow resumes the phase with a one-time grant. The run journal's twin of approval.standing.
phase.askedjournalrunner/runner-control.tsA question was written into the session's stdin and an answer is expected back — phase.answered carries it when it comes.
phase.auto-nudge-refusedjournalrunner/runner.tsThe watchdog meant to nudge and the write did not reach the child — recorded instead of the nudge, because a line claiming a nudge that never happened is the record lying.
phase.auto-nudgedjournalrunner/runner.tsRung 1 of a stall ladder: one line written into the open stdin. scope: 'local' marks the local-job ladder rather than the silent one. Since control-tower phase 111 (#179) scope: 'external' is the external-clock ladder's own rung: a session waiting inside its turn on somebody else's clock is told first, and parked only if the call is still open parkAfterMs (EXTERNAL_PARK_GRACE_MS, five minutes) after a nudge it received.
phase.auto-recycledjournalrunner/runner.tsRung 2 of the silent ladder: the child was ended and the phase re-boarded on its own session id.
phase.awaiting-verificationjournalanalysis/timeline.ts, runner/runner.ts§Verification started. Every stall signal is suppressed until phase.verify closes it — a build is silent and fine. Written on the person-check path only: the timeline measures the AUTOMATIC proof from phase.verify itself (control-tower phase 61, #76).
phase.board-at-boundaryjournalrunner/runner-control.tsA person pressed "board this phase at the next boundary" (board-at-boundary, or resume-phase on a run with its loop behind it — control-tower phase 98, #137): the phase is hinted in the person slot and RESERVED at admission, so the next lane that frees is its own. brief (continue resumes its own session, resume boards fresh with the resume brief), sessionId, the instruction (first 500 characters) and the press's actor, reason included.
trigger.armedjournaltriggers.tsA stored trigger was armed — "when X happens, press Y" (control-tower phase 98, #137): id, when (parseWhen's grammar), verb and its body, mode (once or every), expiresAt, note, and the armer's actor (by, via, origin, remoteUser, reason when given). A STATE when already true fires at once — arming "when the run pauses" on a run that is already paused acts exactly as a polling script's first poll would have.
trigger.firedjournaltriggers.tsA trigger's when came true and it pressed its verb, through the same in-process door the route uses (verb-press.ts, control-tower phase 98, #137): id, when, verb, cause (the observation or state that fired it), mode, the press's ok and status, error when it was refused, by (who armed it) and note. Written AFTER the firing is spent on disk, never before, so a console that dies between the two never presses the verb twice.
trigger.expiredjournaltriggers.tsAn armed trigger reached its expiresAt — at most TRIGGER_MAX_LIFE_MS, a week — without firing (control-tower phase 98, #137): id, when, verb.
trigger.cancelledjournaltriggers.tsA person cancelled an armed trigger before it fired (control-tower phase 98, #137): id, when, verb and the cancelling actor.

| supervisor.detected | journal | pro/supervisor/index.ts | The supervisor's pass (control-tower phase 101, #145) found a stuck state it had not seen before — once per ROOT CAUSE while it holds: situation (a SUPERVISOR_SITUATIONS word — hinted-not-queued, ahead-of-dependency, lane-lost-at-start, hot-account, stale-credential, checkout-refused, replay-at-cap, approval-timed-out, quiet-not-stalled, machine-load, streak-near-max, halted-with-ready-work), rootCause, since (when the state began, not when it was seen), evidence (the sentences that proved it), the remedy row's remedy and the effective policy. Every policy writes it, off included. | | supervisor.suggested | journal | pro/supervisor/index.ts | A remedy was offered as a standing card rather than pressed (#145 B): the policy is suggest, the row's class is suggest, a cap was spent (cap {cap, limit, count}), or the console may not press (refused, --allow-run off). id (the card's), situation, rootCause, verb, body, why. Under observe it carries dryRun: true and no card is raised. A person's Dismiss writes it again with dismissed: true and by. A run request's (turn-by-turn phase 6) carries onItem: true and raises no card: its ONE approval is the person's item already. | | supervisor.acted | journal | pro/supervisor/index.ts | The supervisor pressed a remedy under its act policy (#145 B) — through the same Service method the route calls (verb-press.ts), under the supervisor start door (via: 'supervisor', counted by the start ceiling), at most once per root cause and inside its hourly caps: situation, rootCause, verb, body, the press's ok, status and error. accepted: true with the person's by and via when a person pressed Accept on a card; dryRun: true with would under observe, when nothing was pressed. Every act is also a pinned note on the run (the decision log). Since control-tower phase 136 (#213) an act it pressed is also a row of Your turn's handled log (source supervisor, one per situation and verb per phase, linked to this line), and the three turn situations (turn-unopened, turn-ai-could-do, turn-returned) are acted on through the console's turn act — a pinned note and the item said once more, or the item withdrawn and its waiters resumed told to run its commands themselves. A run request (run-requested, turn-by-turn phase 6) is acted on by answering its ONE approval Approve as the supervisor — the act through the run verb of its name. A refused press (409) still counts against the root cause's caps (#286), so a refused verb is not pressed again every pass. | | supervisor.escalated | journal | pro/supervisor/index.ts | An operator-only act was handed to a person (#145 C): id, situation, rootCause, the exact command (composed from the verb table's cli shape — phase-console run … — or the command outside the console that is the fix, with then, the read that proves it), route for a verb with no CLI shape, deadline, and invariant when a press would have broken one (press-only-halts, no-live-switch, …). An inbox row and a needs-you push carry the same command; the pass never waits for the answer. dryRun: true under observe. Since control-tower phase 136 (#213) the escalation is raised as an ITEM — an operator-act, birth supervisor, with a guide and the proof its detection names — whose one push is the item's own; the card keeps its item, the inbox draws the item, and the item is proven (by watch) and what waited resumes once its proof lands or its cause no longer holds. A run request's escalation (raise-budget, a stop through no-orphan-kill) carries onItem: true and raises no item: the request's ONE approval is the person's. | | supervisor.policy-changed | both | pro/supervisor/index.ts | The supervisor's word changed (#145 F, operator decision 11): from, to, by, at, reason, and slug or situation for a per-plan or per-remedy word — on every run the pass watches and in the console log. late: true when the change was written while no supervisor answered (phase-console supervisor set against a console that was down, or older) and journalled when it next started. | | supervisor.act | both | pro/supervisor/tools.ts | The supervisor CHAT pressed an act the operator asked for (control-tower phase 27, §Architecture 8) — through the SAME Service method the tool's route calls, under pressActor (the operator's door, never counted by the start ceiling) with via: 'supervisor-chat': chat (the chat's id), tool (a CHAT_TOOLS name), method, verb, args (scalars only, each cut at 200), the answer's ok, status and error, and the actor's by, via, door and origin. On the named plan's run when the act names one, and in the console log always. A tool whose capability flag is off writes nothing: it is refused before anything is pressed. | | supervisor.chat-started | log | pro/supervisor/session.ts | A chat's process reported its init frame (control-tower phase 27): chat, sessionId (learned from the frame, never assumed), resumed (it went through the resume gate with --resume), account, thread, model, and effort — the --effort it was started with, null for the machine's default (phase 119). | | supervisor.chat-switched | log | pro/supervisor/session.ts | The model or the effort that answers a chat changed (control-tower phase 119): its new process answers under another pair than the one before it — chat, from and to (each {model, effort}: the model the init frame named, the effort it was given, null for the machine's default), held (the message that brought it waited at the message boundary for a turn in flight to finish — a turn is never cut), resumed (the thread went on with --resume), thread. The chat's ledger writes a switched line at the same moment, which the thread view draws, and every turn line carries the pair that answered it. | | supervisor.chat-thread | log | pro/supervisor/session.ts | A chat started a NEW THREAD rather than resume its session (control-tower phase 27): the resume gate answered fresh — past the 250k-context rule (context-rule), under another account (account-changed) or at its window's wrap-up line — chat, from (the session left behind), reason, contextTokens. The transcript draws the same moment as a thread marker. | | supervisor.chat-cap-raised | log | pro/supervisor/session.ts | A chat's turn ended at its cap (error_max_turns or error_max_budget_usd) and the console raised it — doubled, raiseCap — and continued on a resumed process (control-tower phase 27): chat, cap (turns or dollars), from, to. The caps' source is chat, never the start ceiling's. | | supervisor.chat-ended | log | pro/supervisor/session.ts | The operator closed a chat (control-tower phase 27): chat, by, spentUsd, thread. Its ledger stays until the retention sweep's supervisor-chats rule ages it out. |

| phase.brief | journal | runner/runner-loop.ts | Which boarding brief this attempt was given (fresh · resume · unblock), and what asked for it. | | phase.brief-degraded | journal | runner/runner.ts | The brief that was wanted could not be composed, and what was used instead. | | phase.checkpointed | journal | runner/runner-control.ts | The lane's state was written to disk and its child ended — the primitive under recycle, account switch, park and the context checkpoint (phase.context-wrapup). | | phase.closeout | journal | analysis/timeline.ts, runner/runner-attempt.ts | A closeout session was boarded to finish a phase whose own session stopped without one. | | phase.closeout-done | journal | runner/runner-attempt.ts | That closeout ended — whether it worked, what it cost, and its last words. | | phase.closeout-frozen | journal | runner/runner-attempt.ts | The closeout did not run because the console (or the fleet) was frozen. | | phase.context-wrapup | journal | runner/runner.ts | A phase's own session passed a context line (autopilot-token-drain phase 3, runner/usage.ts), judged on each of its API calls against the window of its model: stage: wrap-up at 0.6 × — the session was told once to finish its step, commit, hand off in-progress and declare partial --reason context; since control-tower phase 46 (#79) the line is written when the notice REACHES the session (delivered: true, with the attempt it took — a refused notice is tried again on each usage event and journalled as phase.wrapup-undelivered until then) — or stage: checkpoint at 0.8 × — the console checkpointed the lane itself (phase.checkpointed and phase.reboard-requested follow) and boards the next attempt fresh with the resume brief, never --resume of that session. context and window in tokens, the fraction, the sessionId; each stage at most once per session, recorded as contextWrapup / contextCheckpoint on the phase record. Never for a closeout, a QA round, a repair or an instructed resume. Lines written before phase 46 may read delivered: false: the notice was then spent whether or not it arrived. | | phase.cost-mismatch | journal | runner/runner-base.ts | A session's BOOKED cost disagrees with what its own calls are worth (control-tower phase 46, #62, CC-5): bookedUsd (what the spawn door booked — the reported total less the session's mark) against pricedUsd (its calls priced at its MODEL's own measured rates, runner/usage.ts TOKEN_PRICES_USD, matched by the whole model id since control-tower phase 109 (#202) — before it, every Opus 5.5 session was priced at Opus 5's rates and read under) by more than a quarter and more than $0.50, with reportedUsd (the CLI's running total), markUsd, direction (over — booked more than the calls explain; under — less), ratio, resumed, calls, model and delegated (a session that started a subagent is never over: its subagents' calls are theirs, not the stream's). Evidence only — it blocks nothing and corrects nothing. No line for a session with no calls, on a model version with no measured row (an alias such as opus names none), or at a ratio the console has already announced as a drift (phase.cost-drift). | | phase.cost-drift | journal | runner/runner-base.ts | A model's PRICE drifted, not its bookings (control-tower phase 109, #202): three fresh sessions of one model within a day, each a phase.cost-mismatch on the same side, their ratios within ±5 % of their median — so the console's price row for that model is what is wrong (a lineup change, a new cache rate). Announced ONCE per model per day, in place of that session's mismatch line, with the session's own figures (bookedUsd, pricedUsd, reportedUsd, markUsd, calls, sessionId, …), model (the price row), ratio (the median), direction, sessions and since (the first of them). For the rest of the day a session at that ratio writes nothing; one off it is still a phase.cost-mismatch, so a re-reported total (#62) is caught on a drifting model too. One memory per console (usage.ts CostDrift), shared by every run. Evidence only. | | phase.control-request | journal | runner/runner-base.ts | The session's stream carried a control_request — the CLI asking its host something (TRS-2): requestId, subtype and tool when it named one. Recorded and never answered: the console is not an SDK host and passes no stdio prompt tool, so a question that arrived this way is on the record rather than lost. | | phase.credential-preflight | journal | runner/runner-loop.ts | The credentials the plan names for this phase were checked before the spawn (ids, held, missing, policy): under require a missing one parks the phase with its errand and nothing is spent; under continue the phase boards, told which are missing, and the run's manifest row reads outstanding. | | phase.declaration-consumed | journal | runner/state.ts, runner/runner-attempt.ts, runner/runner-control.ts, runner/runner-loop.ts, runner/runner.ts, service-base.ts, service-runs.ts | record.declared was deleted, and under which of the four licences (DECLARATION_CONSUMERS): why, the spent status, its reason/watch/at, spentAt, the waits taken and the parkedMs from the parks' own stamps; the new-outcome arms add next, a retry adds by. Every licence writes it — board-closed and retry used to spend testimony in silence (WAI-9). | | phase.declaration-refused | journal | runner/runner-attempt.ts, runner/runner-control.ts, service-runs.ts | A declared word recorded and NOT acted on (WAI-8): why: cap — its count of acts on this phase reached max (DECLARATIONS_MAX_PER_PHASE) — or why: cooldown — a second partial inside cooldownMs of the act that stands (the unsupervised paths only) — or, since 5.0.0, why: watch-missing (TRS-3): a waiting-external declared with no ref after the console refused the session's own wait, on a command the console could neither mint a ref from nor read off the plan's Waits on: line; the phase parks for a person. refused counts these; an operator's Retry clears the ledger. | | phase.disposition | journal | runner/runner-attempt.ts | How an attempt was judged (runner/outcome.ts) and what that judgement will do next. | | phase.done | journal | analysis/timeline.ts, runner/runner-attempt.ts | The board reads this phase done. Terminal. | | phase.errand | journal | converge.ts, runner/runner-attempt.ts, runner/runner-control.ts, runner/runner-loop.ts, runner/runner.ts, service-base.ts, service-recovery.ts, service-runs.ts, service.ts | Something a person must settle, raised against this phase. The errand's actions carry the door. Since control-tower phase 6 an errand about a declared external wall may carry alsoPhases: the phases whose declaration of the same wall folded into it rather than raising errands of their own. Since control-tower phase 39 (#42, #43) it is also written by parkOwnLockBlock (runner/runner-attempt.ts), for a blocked declaration whose only lock was the phase's own — parked for a person, never re-queued behind itself — and for the blocked-declared:protected-path wall, whose errand names the act and the path the session declared. | | phase.errand-folded | journal | runner/runner-attempt.ts, service-recovery.ts | A second declaration of the SAME external wall, from a phase whose scope intersects the first's, joined the standing errand instead of raising its own (control-tower phase 6, #19). The same wall means the fingerprints meet — the reason with its numbers and punctuation folded away, or any pollable ref both declared (wallFingerprint). into names the phase whose errand it joined: that errand's alsoPhases gains this phase, and this phase's recoveries[N].foldedInto points back. situation is the errand key, need the declared reason, and by who folded it on the heal path. It is written with no errand, so nothing is announced, and the run is not parked a second time. A fold whose target errand no longer names the phase is void, and the healer writes that phase an errand of its own. | | phase.errand-cleared | journal | runner/state.ts, service-recovery.ts | A standing errand stopped being true and was retracted. reason: done and reason: skipped (control-tower phase 39, #43) are written by retireSettledErrands (runner/state.ts) once the phase reads done or was skipped by the operator — from the drive loop's reconcile, from both Skip doors and from the read path's scoped-finish correction — with situation the retired errand's key and since when it was raised: an errand that outlives its phase is what kept a finished scope reading open. reason: never-ran is the one the sweep writes by itself: a never-started errand over a phase whose record still reads pending, whose board word is still ready and which holds no queue entry — the ask was false, and withdrew counts the rungs re-settled withdrawn with it, which is what makes the remedy climbable again (#16). reason: accounts-changed (control-tower phase 5, #36) is the heal pass retracting a resource-wall:auth or resource-wall:usage errand once the breaker reads an account usable: forgave counts the resource-wall rungs re-armed with it (forgiven.by: accounts-changed), usable of total is what the breaker said, and alsoRun: true when the run's own errand about the same wall went too. | | phase.external-wait | journal | runner/runner.ts | The lane is parked because a Bash call was waiting on a clock — scope says whose, source whether the call was open or the console refused it, and watch the ref the console minted from the command (since 5.0.0 every shape mintWatchRef reads: a poll loop's condition, a --watch runner's one-shot form, a sleep's clock, a gh watch's run or PR — null for a wait with no honest landing, whose command stays on the card as prose). Since control-tower phase 47 (#52) the command is never the park's watch: with nothing to poll the park rests on its window and its reason says so; between two calls of a chain it mints from the chain's newest wait. Since control-tower phase 111: until — the instant a clock loop's own target names (knownEndOf), which bounds the park instead of the default window (#179); background — the session's own background tasks the checkpoint ends, named (each id and description) and owed to the next session through reverify.jobs (#206). Since one-pool phase 24 (#292): a local wait whose minted cmd: ref reads a path a DETACHED job of the session names (setsid, nohup, disown — LaneSignals.detachedPaths) arms that ref, because the job outlives the checkpoint; no re-run is owed for it. | | phase.failed | journal | analysis/timeline.ts, runner/runner-attempt.ts | The attempt ended red and the ladder now owns it. | | phase.fence-lifted | journal | runner/scheduler.ts | The scope fence over this phase came down (control-tower phase 6, #19). fence names the phase whose wall it was, wall that declaration's instant, and why is a FENCE_LIFT_REASONS word (shared/run-lifecycle.js): landed (a ref landed or the declaration did), retry (the operator retried that phase), release (its lock was released), budget (its wait budget ended) or cleared (it stopped being a wall any other way — skipped, finished, or an errand settled with every ref refused). A queued phase reads pending again and boards on the next pass. Written once per wall per phase, by applyScopeFence. | | phase.fenced | journal | runner/scheduler.ts | This phase will not board yet: another phase of the run is parked on a declared EXTERNAL wall in an intersecting scope (control-tower phase 6, #19). An external wall is needs-human or blocked declared with --needs external, or filed blocked-declared:external. It fences while it can still come down by itself — a live watch ref, or its errand standing inside its wait budget. fence names the holder and refs its live refs, overlaps the scope tokens the two share, until the holder's wait-budget end (null for none) and wall the declaration's instant. The drive loop's candidate filter and the healer both call applyScopeFence, so the two never disagree about a wall, and it writes this line once per wall per phase. A fenced pending phase reads queued with a waitingOn holder of kind fence; any other status keeps its word and gains only the holder. A disjoint phase still boards — that is keep-going, unchanged. | | phase.gate-ai | journal | runner/runner-loop.ts | The phase's gate is one an AI session may clear, and the session was told to clear it first. | | phase.gate-cleared | journal | runner/runner-loop.ts | A gate a live run was holding its phase on reads clear when read again (one-pool phase 23, #239) — on the drive tick's minute (GATE_REREAD_MS) or at once after a docs change in another plan (by: "docs-change"), which is what a plan <slug>:<N> gate reads. {gate, was, by}; the phase is back to pending, and its boarding reads the gate as always. | | phase.gate-delegated | journal | runner/runner-loop.ts | A human-family gate verdict whose verification was delegated to the session — by the plan's gates row, this console's word, or the shipped delegated (decisionKey, source, phase 11); the boot prompt briefs it to evidence each condition and record the clearance. Since control-tower phase 107 (#174) only a gate the plan did NOT mark manual — an overdue deadline — is delegated: a manual gate journals phase.gated with why, whatever the row says. | | phase.gated | journal | analysis/timeline.ts, runner/runner-loop.ts | The phase is held by a gate nobody has cleared. Terminal for this boarding. Since phase 11 a delegated gate that states NO condition a session could evidence stops here too, carrying why, decisionKey and source. | | phase.git-strategy | journal | runner/runner-loop.ts | Which checkout the session was given — branch, worktree, PR intent — and which checkout bullet it was told (lane · mirror · run · detached · shared). | | phase.halted | journal | runner/runner.ts | The phase settled with a halt: the reason and, since P1, its KIND (phase-level halts write record.halt, run-level ones state.halt). | | phase.human-verified | journal | runner/runner.ts | A person answered the verification card themselves — their decision, and who they were. |

| phase.issue-closed | journal | pro/issues/drafts.ts | A session's close draft closed the issue it named, after a person's Approve — a close is always a person's, whatever the plan's word — through the one writer (pro/issues/write.ts, gh issue close with the draft's reason as the comment): id, number, url and repo as owner/repo. Announced under the issue category as what was closed in your name. | | phase.issue-commented | journal | pro/issues/drafts.ts | A comment draft reached its issue — a session's own, or the one the console derived from a duplicate of an issue the estate already holds (phase 12): id, number, url, repo. Filed by the plan's file word under --allow-publish, else by a person's Approve; announced under issue. | | phase.issue-deduped | journal | pro/issues/drafts.ts | A file draft is an issue already known, so it moved to duplicate instead of being filed: an earlier draft of this plan with the same fingerprint (repository, action and the normalised title — never the body), or a cached issue of the estate whose title matches or whose body carries the draft's where beside the provenance footer's mark. id, then the duplicate's own id, number and url as far as they are known. A duplicate of something that EXISTS on GitHub becomes one comment draft on it, written by the console with derivedFrom, which the same sweep then decides on its own and which never spends the budget. Since control-tower phase 115 the repository is searched LIVE before every file draft is filed, not only the console's own (live: true on a line the search answered), and a repeat reaches its issue at most once per run: a second repeat of the same issue in one run writes no comment and says seenAgain: 'already this run'. | | phase.issue-discarded | journal | pro/issues/drafts.ts | A person's No (POST /api/run/<slug>/issues/<id>/discard) on a draft still waiting — pending-approval, pending-landing or drafted: id, by, and number for a comment or a close. A draft the plan's off word discarded writes phase.issue-refused instead. | | phase.issue-drafted | journal | pro/issues/drafts.ts | The sweep — the watcher's arm on issues.ndjson, or the boot pass — met a drafted line for the first time in this process: id, action, repo (the estate KEY the plan's Repos column spells, never owner/repo), a file draft's title or a comment's or close's number, and derivedFrom when the console wrote the line itself. The decision follows in the same pass, so one of the lines below always stands beside it. Written on the plan's live run, else its newest; a plan with no run yet gets no line at all. | | phase.issue-failed | journal | pro/issues/drafts.ts | The draft moved to failed, and reason says what happened: unknown-repo — its repo key is not in this console's estate (at the decision, or at Approve when it has since left; with repo); no-remote — the estate holds it and it has no GitHub remote, so there is nowhere to file; restart — a filing stood longer than FILING_STALE_MS (ten minutes), the console having died mid-gh without recording the answer — never retried, because re-filing is how the duplicate this whole feature exists to prevent gets made, and the note tells a person to look on GitHub before approving again; or the writer's own refusal (not-allowed — --allow-issues off at the moment of the call, --allow-publish implying it, asked every call; bad-request; no-gh; no-auth; rate-limited; failed — gh's stderr as reasonFor reads it, or a create that printed no URL), with detail when it had one and the draft's number. | | phase.issue-filed | journal | pro/issues/drafts.ts | A file draft became an issue on GitHub through the one writer (gh issue create, the body by --body-file; since control-tower phase 115 the labels are its type, severity:<level>, awaiting-plan and from-session, then the draft's own — never phase-console or the plan's slug, which existed on no repository — each created where the repository lacks it, once per repository, and dropped from the filing when it could not be made): id, the number read off the URL gh printed — never guessed — url and repo. Filed at once by the plan's file word under --allow-issues (which --allow-publish implies), inside the per-day filing rate, else by a person's Approve; announced under issue, and the number's provenance (plan, phase, run, draft) is what the repository page's Filed by control and its provenance chip read. | | phase.issue-over-budget | journal | pro/issues/drafts.ts | An OPERATOR ticket's draft past that door's own meters (one draft a ticket, ten a day — ISSUE_BUDGETS.ticket and operatorDay) was refused, terminally. Since control-tower phase 115 nothing else reaches this line: a session's draft over its phase's budget — the script's count or the console's own, inPhase earlier drafts against 3, a suggestion against its own cap of 1 — is HELD for a person (phase.issue-pending, held: budget), never refused, and the per-run cap of 10 is gone; the console's per-day filing rate (held: rate) is what bounds what it files. | | phase.issue-reannounced | journal | pro/issues/drafts.ts | A draft has waited in pending-approval past DRAFT_REANNOUNCE_MS (12 h) and is said once more (control-tower phase 84, #118): id, state, ageMs (since it started waiting) and where when it names a file. Once per draft — an announce line in the plan's issue ledger marks it, so a restart does not say it again; it moves no state. Announced under issue as a draft still waiting for you. | | phase.issue-pending | journal | pro/issues/drafts.ts | The draft is waiting, and state says on what. pending-approval — a person's Approve, Discard or edit in the inbox: under the plan's draft word (mode), or under file with --allow-issues off (mode: file, filing: false, held: flag, the note naming the flag — --allow-publish implies it), or for any close once its landing hold is over — a close is always a person's. pending-landing — a close whose phase has not LANDED yet under a landing policy other than hold (with number); it moves to pending-approval by itself the moment the run record says the phase landed, written again with held: landing. Since control-tower phase 114 two more holds: held: budget — a draft phase-issue.sh HELD over its phase's budget, reviewed once (the plan's word, the estate and the dedupe still apply, and a close still waits for its landing) and then taken as the console's own hold, so it waits for a person whatever the plan says; and held: suggestion — a suggestion, on a console that does not file suggestions by itself (Settings ▸ Issues ▸ "Also file improvement suggestions" off, as it ships), which waits for a person whatever the plan's word. Since control-tower phase 115 the console's own count holds the same way (held: budget, with inPhase), and two more holds stand between a file word and the writer: held: public — a critical or security finding (severity critical, a security label, or a security word in its title; a derived comment as its source) bound for a repository whose visibility is not private or internal (visibility, asked once per repository and cached — unknown reads as public — and repo), which a person decides whether to publish; and held: rate — the console has filed filed issues and comments by itself in the last 24 hours against its per-day rate (20, ISSUE_BUDGETS.dailyFilings; a person's Approve never counts), and the sweep files it in ledger order once the rate allows, while the word is still file and the flag still on. Every pending-approval line is announced under issue as a draft that needs you. | | phase.issue-refused | journal | pro/issues/drafts.ts | The plan's Issues: word for this phase, read at the sweep, is off (mode), so the draft moved to discarded with the note saying so. phase-issue.sh refuses under off before it writes anything, so this answers a line that reached the ledger regardless — the plan's word changed after the draft, or the line was not the script's. |

| phase.ladder-deferred | journal | runner/runner.ts | A rung remains that the drive loop cannot drive but the healer can on a stopped run (a fresh briefed agent, the repair script, an auth or model wall) — deliberately not climbed here, remaining naming the table's untried rungs and next the one the healer will take. Since 5.0.0 never written for a table NOBODY can drive: that is exhaustion, and the errand names the vehicle and why (LFC-2). Since control-tower phase 79 (#98, #114) a deferral is a CARD: the record carries undriven.deferred, the inbox raises it at once with Retry, and the line is written once per deferral — the same situation, next and remaining looked at again after the ladderSeen expiry writes nothing. resource-wall:usage is no longer deferred on a console that can read the quota door: its switch-account and wait-window rungs are the runner's own (phase.rung vehicle: runner). | | phase.ladder-refused | journal | runner/runner.ts, service-recovery.ts | One of the ladder's own caps refused a climb (RCV-6): cap (phase-rungs · phase-usd · run-rungs · run-usd · day-usd), spent and limit in the cap's unit, and the sentence — written once beside the errand it produced, by either climber. Before 5.0.0 a cap refusal was only a sentence in the errand, so three of the five caps had no auditable record of ever refusing anything. | | phase.ladder-replenished | journal | runner/runner-control.ts, service-runs.ts | An operator's Retry replenished the phase's ladder (control-tower phase 5, #14 ask 3): the interruptions and environment-caused records it counted were forgiven (forgiven, split into interrupted and environment, by: operator), so the same-rung-once rule and the rung caps no longer see them — never a verdict, never a dollar. Written by both Retry doors, the live runner's and the stored run's; the console's own retry (a rung, a watch landing) forgives nothing. | | phase.ladder-extended | journal | service-recovery.ts | The phase's rung count was spent but the newest settled rung landed commits, so ONE more rung was granted (ladderExtendOnProgress, off by default; once per phase; the dollar caps stand). | | phase.ladder-skipped | journal | runner/runner.ts | No rung was climbed because auto-recovery is off for this run. | | phase.live-wall | journal | analysis/timeline.ts, runner/runner.ts | A usage wall detected on the STREAM, while the child is still running, rather than from a corpse — by the CLI's status word or, since 5.0.0, by its utilization at WALL_PCT (ACT-7). action is what the run's onLimit did: switch, pause, none (nowhere to go — carrying nones, the count inside LIMIT_NONE_WINDOW_MS), and after LIMIT_NONE_MAX of those, wait (the phase parked on the window until until) or park (no reset known; the errand stands) — never a third none (ACT-6). Since autopilot-token-drain phase 6 a reset further off than LIMIT_ACTION_COOLDOWN_MS — the window's reported reset, else the burst's own retryDelayMs — skips the nones and waits on the FIRST burst; a wait carries trigger: far-reset for that, repeated for the escalation after the nones (H6: P2 of run deadaff9 retried 47 times through two nones on a reset 2 h 48 min away). The account is marked (run.account-cooling) before any of them. | | phase.liveness | journal | runner/runner.ts, runner/runner-attempt.ts | A stall episode CLEARED — the card comes down, with the signals that now read healthy; reason: 'lane-ended' when the lane ended and took its stall with it (control-tower phase 47, #80); reason: 'new-attempt' with attempt when the lane booted its next attempt in place and the dead session's episode ended with it (control-tower phase 80, #99 — runner/runner-attempt.ts, freshAttemptLiveness). | | phase.lock-cap-rearmed | journal | converge.ts, runner/runner.ts | The lock a capped waiter was queued behind is gone, so its two-hour wait starts over. | | phase.lock-debris-released | journal | runner/runner-loop.ts | A lock whose holder is provably gone was released at boarding. | | phase.lock-lost | journal | runner/runner.ts | The lease keepalive was REFUSED by a claim naming another owner (holder), so this lane no longer holds its phase and was STOPPED (stopped). Only an explicit refusal from a run that ended by itself stands a lane down (control-tower phase 56, #77); the line carries the exit code and timedOut it acted on, so a stand-down can be checked against what the script answered. | | phase.isolation-refused | journal | runner/runner-loop.ts | The phase's OWN Repos cell names a repository outside the run's root (control-tower phase 82, #94): refusal: scope-outside-root, the tokens outside, and a reason. The run keeps the checkout it took on the cells the root contains; this phase runs in it with an unqualified claim (qualificationFor), serialised against every claim it intersects. Once per phase per run. Before phase 82 one such cell refused isolation for every phase of the run (run.isolation). |

| phase.pool-policy | journal | runner/runner-control.ts | A phase boarded under a pool policy other than the last boarding's (one-pool phase 11): a live poolPolicy patch, or a phase's own - **Pool policy:** word or launch option, takes effect at that phase's boundary (poolAtBoundary) — from, to, the source that said it (POOL_POLICY_SOURCES) and words, the policy's own sentence (POOL_POLICY_META). |

| phase.reconcile-held | journal | runner/runner.ts | Reconcile did NOT close a record the board reads done, because the handoff it reads is not committed — untracked, or changed in the working tree (control-tower phase 79, #113): reason, files. Only committed handoff content closes a record; the docs root's git status of docs/handoffs/<slug> is the one read, and a git that cannot answer keeps the old close. Once per file state, not once per tick. | | phase.reopened | journal | runner/runner.ts | A record reconcile had closed on the board's word went back to pending because the board no longer reads it done (control-tower phase 79, #113): by: reconcile, board (the word now), closedAt, resumeSessionId (kept, so the ladder resumes that session). Only a record reconcile closed (its reconciled stamp) is ever reopened — never one this run verified — and a board word that is absent or unknown reopens nothing. | | phase.safeguard-flag | journal | runner/runner-attempt.ts | The API's safeguards flagged the session (control-tower phase 111, #177) — the banner safeguards flagged this message, never the model declining the task: classifier (the Details: [<classifier>] token), requestId and messageId to report the false positive, flags this phase has met and the remedy it took — fresh-session (a new session with the resume brief, never --resume of the flagged conversation), switch-model (the second flag, only under a ladder model policy) or needs-human (the third, worded with what was tried). | | phase.undriven | journal | runner/runner.ts | A phase of a live run that nothing drives — no lane, no queue entry, no boarding hint, no park, no errand — while the board reads it in-progress/stuck (control-tower phase 79, #114): board, situation, why. Written by the drive tick once per episode; the record's undriven stamp holds since, GET /api/runs lists it per run (undrivenPhases), and the inbox raises an undriven stall after STALL_META.undriven.afterMs (30 min) — at once when the ladder deferred it. Since one-pool phase 24 (#219) a phase serial behind a live lane of its own run (serialBehind) is driven, and a boarding hint younger than UNDRIVEN_HINT_GRACE_MS (60 s) has not met the candidate pass yet — so a wrap-up's re-board is no longer stamped undriven seconds before phase.serial-behind places it. | | phase.lock-provisional | journal | runner/runner-loop.ts | The runner claimed the phase at grant, on a short lease, to cover the window before its session claims for itself. | | phase.lock-mirror | journal | runner/runner.ts | The console mirrored the phase's lock to git outside the session's turn (control-tower phase 63): verb (claim after its provisional claim, release after every release), ok (false when the commit failed or the docs root's critical section stayed held — the lock is still on disk and the next mirror commits it), ms from the ask to the answer, and the script's last line. One commit of that lock path alone; never a pull, never a push. | | phase.lock-race | journal | runner/runner-loop.ts | Two boardings wanted one scope and this one lost; it backs off and queues. | | phase.lock-refresh-unanswered | journal | runner/runner.ts | A lease refresh gave no verdict — it timed out (why: timed-out), crashed, or exited without naming another holder (no-verdict) — so the lane keeps its session and the next tick retries (#77). code, timedOut and the script's detail ride the line. The 5400 s lease has eight missable cadences of headroom; a string of these means the host is starved, not that the lock is gone. | | phase.lock-refreshed | journal | runner/runner.ts | The lease keepalive renewed the claim — the script said lock refreshed for <owner>, even from a run killed at its ceiling afterwards. | | phase.lock-refused | journal | runner/runner-loop.ts | phase-lock.sh claim said no, and who holds it. | | phase.lock-wait-capped | journal | runner/runner-control.ts, runner/runner-loop.ts | A waiter hit the two-hour cap and parked rather than queueing for ever — for the ONE shape isCappableBlocker allows. Since control-tower phase 60 (#82) the admission cap re-scans when it fires and parks only if a cappable holder STILL blocks, naming that holder (slug, phase) with its lease as it stands then; an autopilot lane whose run holds its grant or runs its session reads live and is never cappable. | | phase.mcp | journal | runner/runner-attempt.ts | The MCP servers this phase was attached to, and probes — how many claude processes boarding this phase started to check them (SLF-3): 0 when the health clock's answer was still fresh, which is the usual case since the preflight reads the clock's cache and shares its in-flight probe. | | phase.mcp-config-failed | journal | runner/runner-attempt.ts | The MCP config for this phase could not be written; the session runs without those servers. | | phase.mcp-degraded | journal | runner/runner-loop.ts | Some named servers would not connect, so the phase boards without them and owes an errand. | | phase.mcp-preflight-parked | journal | analysis/timeline.ts, runner/runner-loop.ts | MCP policy: require and a server would not connect, so the phase parked instead of boarding. | | phase.mcp-preflight-skipped | journal | runner/runner-attempt.ts | The MCP probe itself could not run, so nothing was concluded from it. | | phase.mcp-require-timeout | journal | runner/runner-control.ts, service.ts | A require phase waited out its window for a server nobody signed in. | | phase.mcp-unmanaged | journal | runner/runner-attempt.ts | The named servers are not in this console's registry, so it attached nothing and said so. |

| phase.message-deferred | journal | pro/messaging/mailbox.ts | A message could not be carried YET and is waiting: a boot note whose phase has not boarded, or a transport that came back unreachable while the recipient may still return. Carries to, deliver and, for a transport attempt, the result that deferred it. Written ONCE per message rather than once per sweep — a note waiting three days for its phase would otherwise be three days of lines. | | phase.message-delivered | journal | pro/messaging/mailbox.ts | The message reached its recipient: via names which transport (socket · stdin · boot-prompt · inbox) and ms how long the attempt took. On socket this means the bytes went in and nothing more — the CLI's inbox writes nothing back on any outcome (phase 1, arm S-B), so only an acked state says the recipient read it. | | phase.message-expired | journal | pro/messaging/mailbox.ts | The message was past its own expires, or past the six-hour live TTL for a now/next-turn delivery, before it could be carried. A boot note never expires this way: it is deliberately for whenever its phase boards, which on a slow plan is days. | | phase.message-refused | journal | pro/messaging/mailbox.ts | The console would not carry it, and reason is a MESSAGE_REFUSALS word — each a different repair: no-recipient (nobody, a done phase, or a live session with no route), not-live, over-budget (16 sends, 8 asks or 6 in a minute), too-large, self-addressed, unknown-scheme, duplicate. | | phase.message-replied | journal | pro/messaging/mailbox.ts | A message answered an earlier ask, carrying its id as replyTo. | | phase.message-sent | journal | pro/messaging/mailbox.ts | A session (or the operator) wrote a message into the plan's mailbox: id, to, from, kind, deliver and bytes. Written for every message, refusals included — a refusal nobody can read is indistinguishable from a message never sent. Also written when a message goes down a lane's stdin, with via: 'stdin', because that transport's delivery is the CLI's later echo tick and not the write. Since (control-tower phase 96, #142) also text, the message's first 1,000 characters (SENT_TEXT_MAX), with truncated: true when it was cut — bytes is still the whole. | | phase.messages-booted | journal | pro/messaging/mailbox.ts | A phase's boarding prompt carried mail left for it: ids and count, plus the sessionId it boarded as. The prompt IS the transport here, so each named message is marked delivered(boot-prompt) as the block is composed — there is no second signal. |

| phase.model-differs | journal | runner/runner.ts | The model the CLI reports is not the model the phase asked for. | | phase.model-mapped | journal | runner/runner-base.ts | The spawn door sent a session another id than the one asked, because the run's account is an API-key account that serves it under its own name (one-pool phase 5): {mode, asked, sent, account} — opus on a DeepSeek key is sent as deepseek-v4-pro. A frame naming the sent model is the model asked (ranAsAsked), never phase.model-differs nor a pinned mismatch. | | phase.model-unserved | journal | runner/runner-base.ts | The spawn door refused a session because nothing on the run's account serves its model (one-pool phase 5) — a provider id on a Claude login, or another provider's id on an API-key account: {mode, model, account, reason}. No child was started and nothing was spent; the run halts models-exhausted with the same words. | | phase.model-escalated | journal | runner/runner-loop.ts | The ladder moved this phase to a stronger model for the next attempt. | | phase.model-switch | journal | runner/runner-attempt.ts | The attempt's disposition moved the model mid-phase. | | phase.model-window-retry | journal | runner/runner-attempt.ts | A model-specific usage window closed and the attempt was retried on another. | | phase.model-window-wait | journal | analysis/timeline.ts, runner/runner-attempt.ts | Every model is inside its own usage window, so the phase waits for the earliest to open. | | phase.needs-human | journal | runner/runner-attempt.ts | The phase parked on something only a person can settle. | | phase.connectivity-wait | journal | runner/runner-attempt.ts | This machine could not reach the API, so the lane is waiting it out: attempt, outage (how many this phase has met), waitMs from the series 1·2·5·10·15 minutes (CONNECTIVITY_BACKOFF_MS) — since control-tower phase 80 (#108) the BACKSTOP, the latest the lane sleeps, while the connectivity probe looks every probeEveryMs (CONNECTIVITY_PROBE_MS, 60 s) and wakes it at the first answer — until (that backstop), the reason, and class: certificate when the stop was certificate-shaped. Charges NOTHING — no attempt, no failure streak, no ladder rung — and the total is bounded by MAX_AUTO_WAIT_MS, past which it falls through to the ordinary failure path. | | phase.connectivity-restored | journal | runner/runner-attempt.ts | An outage's wait ended and the lane has decided where it goes BEFORE anything boards (control-tower phase 80, #108): outage, by (probe — the connectivity probe heard the API; clock — the backstop elapsed), waitedMs, and next — closeout (the board reads the phase done: its handoff went in complete before the network went, so the closeout a clean ending gets runs and no session boots), resume with session (the session that lost the API is resumed with a continuation, never the start boot), reboard (that session is not worth resuming; the phase re-boards with the resume brief) or fresh (it never reached the API — the boot prompt is self-contained). Charges nothing. | | phase.credential-wall-seen | journal | runner/runner-attempt.ts | A SECOND lane met the wall the run is already halted on: class, account, reason. It marks its own record and does nothing else — no second retirement, no second errand, no re-halt and no charge, because one wall reaching two readers is one event. | | phase.not-started | journal | analysis/timeline.ts, runner/runner-loop.ts | The boarding stopped before the session began — a gate, a lock, a preflight. | | phase.notes-booted | journal | runner/runner-loop.ts | The boarding prompt carried forward notes: count, bytes, the kinds (handoff, deferral, message, trailer) and the ids of the ledger lines among them. The block is the engine's — every word of a boot prompt is — so this is the only record that a note somebody wrote was actually handed to a session. | | phase.outcome | journal | runner/runner-control.ts, runner/runner.ts, service-runs.ts | A phase-outcome.sh declaration was read: what it said, and any clock it named. via: 'armed-file' is the one a SPAWN found already sitting in PE_OUTCOME_FILE (5.2.0, #21 §3) — the last word of the session that wrote it, read and journalled before the pre-spawn delete that used to destroy it unread. A complete or waiting-external found that way is applied rather than re-run, so the resume rung it was about never spawns. Since control-tower phase 39 (#42) a screened declaration adds what the screen did: refused (the lock: refs naming the declaring phase's own lock, dropped), and for a block on a person (--needs ambiguity) person: true, clock (the sentence saying it takes no watch and no wait clock) and dropped (the watch and resumeAfter it asked for and did not get). A declaration the screen did not touch keeps its old shape. | | phase.outcome-ignored | journal | runner/runner-control.ts, service-runs.ts | A declaration was read and deliberately not acted on. From the live runner, reason is a sentence (a lane is working the phase, the record is done). From the inbox (WAI-7), reason is stale (past the 24 h rule), invalid (the reader rejected it) or failed (its act threw), with writtenAt, ageMs, the file and where it was kept under outcomes/ignored/ — set aside on disk, never deleted. | | phase.outcome-lock-blocked | journal | runner/runner-attempt.ts | The session declared blocked on a lock, with the lock: ref that will free it. | | phase.outcome-needs-human | journal | runner/runner-attempt.ts | The session declared needs-human; the phase parks and the errand stands. | | phase.human-step | journal | runner/runner-attempt.ts, runner/runner-control.ts, service-runs.ts | A needs-human declaration carried a HUMAN STEP (control-tower phase 41) and the console recorded it: stepId, kind, where, birth, state (notified once the one push went out), the redacted title, and proof and until when the step has them — never a code or a secret. The phase waits on a person from here: an unbudgeted wait of kind person, situation blocked-declared:human-acts. The step itself is in the instance's human-steps.ndjson. | | phase.human-step-due | journal | service-recovery.ts | An upcoming human step's due-when ref LANDED (control-tower phase 121, #182): stepId, kind, ref (the due-when ref), detail (what the ref read, or null), pushed and — when the watch clock REFUSED the ref, which can then never land — refused: true, the act made due at once with the refusal named rather than held back unseen. The step is now declared and its ONE notification went out — Now: <the act>, the command kept off the lock screen (turn-by-turn phase 3) — and its window starts here. Written by the operator-acts pass (humanStepDuePass), which asks every upcoming step's ref at its scheme's own cadence. | | phase.human-step-opened | journal | service-recovery.ts, service-runs.ts | A person opened a human step — or opened it again (control-tower phase 43): stepId, kind, n (every open counted, Open again included), where (here — the link answered for the caller's own browser; host — opened on the machine, behind --allow-terminal/--allow-agent and only after the caller sent the full URL back; terminal — the command in the embedded terminal), what (url or command) and by. The launch door writes it too (control-tower phase 139): a plan's auto-open: host step opened on the machine for a link the launch form showed in full, door: launch, where: host, by the person who pressed Start — on the run it launched, once that run has an id (the step was born before the run). n is the ledger's count, 0 when the step had already settled and the ledger would not count it. | | phase.human-step-open-skipped | journal | service-runs.ts | The launch door could not open a plan's auto-open: host step on the machine (control-tower phase 139): stepId, kind, where: host, door: launch, by (the person who pressed Start) and why — the opener's own words, or that the machine has no desktop opener. The launch went on and the step stands, notified, for a person to open. | | phase.human-step-checked | journal | service-recovery.ts | A check of a human step's proof did NOT land (control-tower phase 43): stepId, kind, landed: false, proof (the ref, or null), read — what the proof answered, in its own words, which the step card says — and by (a person, or terminal for a command that exited non-zero). The step waits on. | | phase.human-step-proven | journal | service-recovery.ts | A human step's proof LANDED (control-tower phase 43): stepId, kind, proof (null when the step named none and a person's word or the terminal's exit proved it), read and by (a person; watch — its proof landed on the watch clock; terminal — its command exited 0). The phase's own session is resumed through the declared-resume path, told what was proven (phase.resume-automatic carries stepId). Since control-tower phase 134 a pass is a VERDICT: the line adds verdictBy (probe, checker or owner — an attest's word or an override, marked unverified), attempt and, for the checker's pass, costUsd, turns, model; every waiter is resumed once. | | phase.human-step-snoozed | journal | service-recovery.ts | A person snoozed a human step's reminders (control-tower phase 43): stepId, kind, until (an hour when unsaid, a day at most) and by. | | phase.human-step-cannot | journal | service-recovery.ts | A person pressed I can't do this on a human step (control-tower phase 43): stepId, kind, reason (redacted like every ledger string) and by. The step settles at once and the park's errand is rewritten carrying the reason — never a loop, no reminder after it. | | phase.human-step-dismissed | journal | service-recovery.ts | A person withdrew a human step (control-tower phase 43): stepId, kind, note, by. The phase's park stands and its errand says so. (The reminder clock's own withdrawals — the phase closed, or moved on — are ledger lines, not journal lines.) | | phase.human-step-answered | journal | service-recovery.ts | A person answered a decision item (control-tower phase 133, #210): stepId, kind, option (the option id, or null for a note alone), noted (whether a note came with it — the note itself is on the ledger, not here), by, door (the door the press proved) and decisionKey (the ## Decisions key its answer was written under through decisions.sh, else null). The item is proven by the answer, and every waiter is resumed once: "The operator answered <option>: ". Since one-pool phase 13 (#367, #368) a decision whose proof the console reads is CHECKED instead: the line carries checked: true and, when the proof names a place an answer can go (a grep '^<key>: answered' <file> or test -f <file> proof), wrote — the file the answer was written to, always in the plan's own docs/handoffs/<slug>/ folder, through no link, a test -f file only ever created; the item reads Checking, and its check's own line (phase.human-step-proven or phase.human-step-returned) follows. | | phase.human-step-declined | journal | service-recovery.ts | A person declined an item that allows it — Not doing this (control-tower phase 133): stepId, kind, by, door. The item settles declined (the console's own withdrawal stays dismissed), and every waiter is resumed once: "The operator declined: . Do not ask again; find another way inside the plan or say what remains". Since control-tower phase 135 (#212) Deny on a permission item writes it too, with denied: true: the note reads denied[: <reason>], the card behind the item (the broker's held call or the widen rung's) is answered deny, and each waiter is resumed once with "Denied — do not retry; find another way inside the plan or say what remains". | | phase.human-step-asked | journal | service-recovery.ts | A person asked a question about an open item (control-tower phase 133): stepId, kind, by, door, chars (its length — the question is on the ledger). Nothing is resumed for it; the raising session is handed it with its next resume ("The person asked: …"). | | phase.human-step-evidence | journal | service-recovery.ts | A person attached evidence to an item (control-tower phase 133): stepId, kind (the item's), evidence (note, image or file), bytes, attempt (the check it belongs to) and by — never its bytes, its name or its hash. The bytes are in turn-evidence/ by content hash, 0600; nothing is resumed and nothing is pushed. | | phase.human-step-check-started | journal | service-recovery.ts | A check of a judgement item began (control-tower phase 134, #211): stepId, kind, attempt, model (resolved through scripts/models.env — checkModel, the alias sonnet shipped), effort (checkEffort, low) and by. The checking session reads the attempt's submission; its verdict lands as phase.human-step-proven or phase.human-step-returned, or the check could not run (phase.human-step-check-skipped). | | phase.human-step-check-skipped | journal | service-recovery.ts | A judgement check produced NO verdict (control-tower phase 134): stepId, kind, attempt, why — the console was frozen, the start ceiling was full, the session ended with no fenced verdict block or ran out of its five minutes — and, when the session ran, costUsd and turns. Nothing was decided and nothing fabricated: the item goes back to waiting, saying the check could not run. Since turn-by-turn phase 5 also why: superseded — a probe's or a terminal's verdict that arrived for an attempt no longer being checked (the person pressed Undo, or a newer check began) was dropped; nothing moved. Since one-pool phase 13 (#374) also why: unrunnable — a probe proof that ran and could not start (exit 126 or 127: its script missing from the console's checkout, or not executable), with read (what is missing, and where) and proof: never a rejection, no Redo counted, nothing resumed; the item goes back to To do, or Redo when an earlier check sent it back. | | phase.human-step-returned | journal | service-recovery.ts | A check sent an item BACK (control-tower phase 134): stepId, kind, verdict (rejected or needs-info), by (probe or checker), attempt, redo (how many things to redo — the words are on the ledger) and, for a checker's verdict, costUsd, turns, model. The item is returned; the person is told once; nothing is resumed. | | phase.human-step-escalated | journal | service-recovery.ts | An item's rejections reached turnEscalateAfter (3) and escalated it to the owner — ONCE (control-tower phase 134): stepId, kind, rejections, after, attempts. The push says so; the item shows every attempt side by side with three ways out — rewrite the guide, I can't, Accept anyway. Since control-tower phase 136 the round escalates an item returned that many times that never escalated (the preference lowered after its rejections): by: round, the item back to notified with its one push. | | phase.human-step-overridden | journal | service-recovery.ts | The owner's Accept anyway (control-tower phase 134) — the one route that writes a verdict: stepId, kind, by, door, attempts. The item is proven by the owner, marked unverified, and every waiter is resumed as on a pass. | | phase.human-step-undone | journal | service-recovery.ts | A person took a running check back — Undo (turn-by-turn phase 5): stepId, kind, attempt, to (opened — To do again — or returned — Redo, when an earlier check had sent it back) and by. A judgement still reading is stopped, and any verdict that arrives for that attempt afterwards is dropped (phase.human-step-check-skipped, why: superseded). | | phase.human-step-rewrite | journal | service-recovery.ts | The escalation's Rewrite the guide (control-tower phase 134): stepId, kind, by, attempts, verdicts. The item is withdrawn and the session that raised it resumed once with every rejection, told to raise a new version. | | phase.human-step-expired | journal | service-recovery.ts | A human step's window closed with nothing proven (control-tower phase 43): stepId, kind, until (its own window, else seven days after it was declared). The step reads expired, its proof leaves the watch, and the park's errand is rewritten — once. | | phase.human-step-reminded | journal | service-recovery.ts | The reminder clock re-announced a waiting human step (control-tower phase 43): stepId, kind, n (this reminder's number — +15 m, +1 h, +6 h, then daily) and pushed. Quiet hours (reminderQuiet) and a snooze defer one; nothing reminds a settled step. Since control-tower phase 136 by: supervisor is the supervisor's turn-unopened act saying an unopened item once more. | | phase.human-step-suspected | journal | runner/runner.ts | A silent stall episode opened on a lane whose LAST output — its own words, or a call of its own answering — named an http(s) link beside waiting words (control-tower phase 44, §Architecture 12's third birth channel): kind (what it reads as — browser-login, device-code, email-link, captcha, third-party-approval), url (redacted — a URL query secret keeps its name and loses its value), words (the phrase that matched, SUSPECT_WAIT_WORDS), where, at (when it was said), since and attempt. Once per episode. Nothing acts on it: the inbox's silent row offers Make it a person's turn, and only that press converts it (phase.human-step-converted). Never a code. | | phase.human-step-converted | journal | service-recovery.ts | A person turned a silent lane's suspected turn into a human step (control-tower phase 44, POST /api/human-steps/suspected): the step's fields (stepId, kind, where, birth: console, state, the redacted title), by, the url and words the reading saw, and suspectedAt. The lane is not parked — its session is still waiting on that link and carries on by itself once the person is done; proving the step settles it. A second press answers the same step and writes no second line. Since control-tower phase 135 (#212) I'll do it myself on a permission item writes it too (POST /api/human-steps/:id/convert), with from: permission, into (the new operator-act item's id) and kind: the permission item is withdrawn as replaced, the act's guide is the refused command in both copy forms, and its I've done this resumes the session through the lane's errand. | | phase.outcome-no-defect | journal | runner/runner-attempt.ts | A repair session found nothing wrong. Deliberately NOT fixed — and it does not retire the halt. | | phase.outcome-partial | journal | runner/runner-attempt.ts | The session declared partial: work remains, resume it rather than re-run it. wrapup: true (control-tower phase 5) when it was the declaration the console's wrap-up notice asked for — its resume is phase.resume-automatic {path: wrapup}, not a rung (climbed: false). | | phase.outcome-superseded | journal | runner/runner-attempt.ts | A declaration arrived about a phase the board already reads done. | | phase.peer-race | journal | runner/runner-loop.ts | Boarding found a Claude session in this repository that holds no lock but could be about to work the phase (zero-touch phase 16, REG-3) — the first minutes of a hand session, before its claim, bounded by its claim window (PEER_CLAIM_WINDOW_MS after its newest start) — so the phase went back to the queue behind it instead of spawning beside it: session, pid, cwd, owner, presence and the session's inferred scope, with the lock race's backoffMs. Never a park and never a release; the scheduler's admission names the same peer as a session holder. | | phase.permission-denied | journal | runner/runner.ts | The CLI's own permission system refused a tool call: tool, target, and — from the stream's best-effort announcement — reason and reasonType; source says whether the stream said so or only the result's authoritative permission_denials did. One line per denial. Not the console's hook refusing (that is phase.tool-denied). Since control-tower phase 135 (#212) each one is also a WALL on the lane's record (PhaseRecord.walls): an MCP tool not granted is mcp, the CLI's copy of a deny rule (reasonType: rule) deny, its classifier classifier, its sandbox sandbox, anything else a tool outside the allow list (allow-list); a hook denial is the console's own and is not recorded twice. | | phase.permission-request | journal | service.ts | A relay-armed session's PermissionRequest hook call arrived (phase 14, spike S2): the tool, what it was aimed at (target, bounded), the sessionId, the CLI's permissionMode and how many permission suggestions it offered. The call is then classified by the same path a PreToolUse call takes — a question goes to the relay — and answered allow or deny, never ask. | | phase.plan-approved | journal | runner/runner.ts, service.ts | A plan a plan-mode phase presented was approved (control-tower phase 11, #34): by a person pressing Approve — the SAME session then resumes through the recover verb, in acceptEdits — or by: policy when the plan's plan-approval: continue answered at the hook and let the call through (source says which level). sha names the plan. | | phase.plan-hold-interrupted | journal | runner/runner.ts | A session whose plan was held was still running graceMs after the hold, so the console interrupted it through the signal ladder (pid); its attempt ends into the declaration the console wrote for it. The measured session ended its turn at once — this is the backstop. | | phase.plan-host | journal | runner/runner-base.ts | A plan-mode phase's session on a run whose relay is not armed was given the relay's presence-only host, because the CLI offers ExitPlanMode only to a session that has one (spike exit-plan-mode.json): armed, the CLI version and floor, and refused (version-unknown, below-floor or no-settings) when it could not be — that session then has no ExitPlanMode and its plan has nobody to be handed to. | | phase.plan-presented | journal | runner/runner.ts | A plan-mode session handed its plan over through ExitPlanMode and the hook held it (control-tower phase 11, #34): bytes and sha (sha256 of the text as presented), the path the console kept it at beside the run's outcome files, the CLI's own planFilePath, and truncated past 512 KiB. The plan's plan-approval decision then answers — hold parks the phase plan-approval for a person, continue approves it at once. | | phase.plan-rejected | journal | service.ts | A person pressed Reject on a held plan: by, the plan's sha and their reason. The phase stays parked with its errand for their next move — Resume with an instruction, or Retry. | | phase.policy-answered | journal | runner/runner.ts, service-recovery.ts, service.ts | The ladder reached the ask a person would have been sent and the policy table answered it instead (Tier 1): the manifest row (decisionKey), the word in force (answer) and where it came from (source: the run, the plan, this console, the shipped default). No card, no push — the act the word names follows (a waiver under qa.exhausted: waive; nothing more for a peer's lock under waits: window). Since 5.0.0 also a QUESTION the console held (decision: hold, class: question, TRS-1): a call in the question class (AskUserQuestion) answered by the plan's ambiguity row with its tool and the questions it carried — the session is told to decide and record a ruling, or to declare needs-human. | | phase.poll-loop | journal | service.ts | The poll-loop guard opened an episode (autopilot-token-drain phase 2): the session's own status checks — ListAgents, TaskOutput that does not block, BashOutput, a Bash clock/process probe or log read, a repeated command — reached six inside two minutes with no other call between, so the hook refuses them until the session makes a different call or goes quiet for two minutes (shared/poll-loop.js). Once per episode: calls and windowMs so far, the tools seen, firstAt, and nudged — whether the one notice a lane is sent reached the session. Each refused check is its own phase.tool-denied with rule: poll-loop. | | phase.pr-session | journal | runner/runner-loop.ts | A session was boarded to open the run's pull request. | | phase.pr-session-done | journal | runner/runner-loop.ts | That PR session ended — cost, turns, and what it said. | | phase.pr-session-failed | journal | runner/runner-loop.ts | The PR session threw before it could open anything. | | phase.progress | journal | runner/runner.ts | How far the active task's long operation has got, as the session said it with phase-outcome.sh <slug> <N> progress --label <text> --done <n> --of <m> (control-tower phase 95, #163): label, done, of, at (when the session said it) and task — the id in progress at that point of the task channel (PE_TASKS_FILE, where the line rides). Read at the next tool result, not at exit, and kept on the phase's record as progress; the phase report (GET /api/run/:slug/phase/:n/report) draws the operation's bar and its rate from these lines. A malformed call exits 2 and writes nothing, and the reader refuses a hand-written line the script would have refused. | | phase.qa-exhausted | journal | runner/runner-attempt.ts | The QA round budget is spent: the rounds, the last report, what they cost, the errand — and, since phase 11, the plan's word for it (policy: waive · halt · an owner, policySource), which decides whether a waiver is recorded, the run halts, or the errand names its owner. | | phase.qa-mode | journal | service.ts | The plan's QA regime was switched from the console while this run was live — the directive written (on, off, or inherit for one phase) and the regime the engine reads back for the plan and the phase, so a verdict that starts or stops holding dependents mid-run has its reason on the record. | | phase.qa-recover | journal | runner/qa-recover.ts | A QA recovery started on this phase — the verb (qa-recover or qa-rerun), the verdict it is answering, the fix strategy, the round budget and who asked. | | phase.qa-recovered | journal | runner/qa-recover.ts | A QA recovery ended with the gate OPEN — the verdict that released it, how many rounds failed first, and what the loop spent. | | phase.qa-round | journal | runner/qa-recover.ts | One round of a QA recovery ended: which round, the verdict it recorded, its report, what it cost, and whether the fix session resumed or boarded fresh. | | phase.qa-session | journal | runner/runner-attempt.ts | A fresh-context QA subagent session was boarded over this phase's diff — with the round it produces, the report it was told to write, and the brief it was sent. | | phase.qa-session-done | journal | runner/runner-attempt.ts | The QA session ended, with the verdict it recorded (or pending if it recorded none), the round and report it landed, and how many rounds this phase has now had. | | phase.qa-session-skipped | journal | runner/runner-attempt.ts | No QA session was boarded, and why. | | phase.qa-waived | journal | runner/qa-recover.ts, runner/runner.ts, service-recovery.ts | This phase's verdict was waived — by an operator with the reason they gave and the round it answers, or by policy (by: policy, decisionKey: qa.exhausted, the source of the word) once the round budget was spent. The gate stops holding dependents from the next board read. | | phase.question-raised | journal | relay.ts | A question a session asked on a relay-armed run went in front of a person for its window (phase 14, QRL-6): tool, the call's key (its first question's), questions — one entry per question, a call carries 1 to 4, each with its own key, option labels and multiSelect — source (where the run's relay answer came from), mechanism (pre-tool-use or permission-request), the card's approvalId and, on a PreToolUse call, toolUseId. A session-ask push goes with it. | | phase.question-answered | journal | relay.ts | One question of a relayed call was answered: key, question, the option chosen (answer), by — human inside the window, else the console's order: rule (with ruleId), recommended, first-option — and waitedMs from the raise (at most the window for a live answer; spanning the outage for one answered at boot, marked recovered). deferred marks a question whose session ended on defer and receives the answer when it resumes. A ruling row keyed ambiguity is appended beside it. | | phase.question-unanswerable | journal | relay.ts | A relayed question the console will not answer by rule, before any window opened: reason — deny-list (the call's tool, or a denied command in an option; rule names it), multi-select, destructive-option (option names it), run-stopped, repeated-key or budget-spent — with the call's questions. The phase is parked needs-human, one push goes to needs-you, and the session is told to hand off and declare rather than guess. | | phase.question-deferred | journal | relay.ts | The console went away — a shutdown, or the run's loop ending — with a question's window open, and answered its PreToolUse hook defer (spike S3): the session ends its turn with the call kept (toolUseId), the card stays on disk, and the question is answered when its window closes (or at the next boot) and given to the session when it resumes. why says which going-away it was. | | phase.queued | journal | analysis/timeline.ts, runner/runner-control.ts | The phase is waiting for a scope somebody else holds, naming the head of the queue. headKind is the head holder's kind; a branch head (control-tower phase 40, #41) is a repository of the scope standing on another open run's branch in the SHARED checkout, and its waitingOn item carries kind, repo, branch and run. Since control-tower phase 60 (#64, #81) it opens a queue EPISODE and carries headClass (HOLDER_CLASSES: own-run, other-run, hand, clock) and, when the wait began earlier and a withdrawal or restart interrupted it, age — the instant its entry first joined a queue, which the new entry is born with. Written at entry, or later when a scan finds a phase that joined free now held. A phase behind a LIVE lane of its own run never gets this line: it is phase.serial-behind. | | phase.queue-closed | journal | analysis/timeline.ts, runner/runner-control.ts, runner/runner-loop.ts | A queue episode ended (control-tower phase 60, #81). outcome is one of QUEUE_OUTCOMES — admitted, withdrawn (why: pause, halt, park, stop, or serial when its holders became its own run's live lane), restarted (the console holding the queue went away; ended where it was last seen waiting, queueSeenAt) or capped. ms is the episode, waitedMs the current wait across its episodes, queuedMs the phase's cumulative queued time, since when the episode opened. Exactly once per episode; a withdrawn or restarted one keeps the entry's age and aging reservation on the record. The timeline's queued bar runs from the episode's phase.queued to this line — to since + ms for a restarted close (control-tower phase 61). | | phase.lane-pinned | journal | runner/queue-control.ts, runner/runner-control.ts, service-runs.ts | A person pinned this phase next in its plan (control-tower phase 100, #135 B.8): while it waits in the queue for a lane, none of its run's other entries boards ahead of it — they wait on the pinned clock holder naming it, who pinned it and why — and its run boards it first after a person's own re-board. Another run is never held. The actor as on phase.queue-bumped. Durable on queueControl.pin, spent when the phase is admitted (phase.admitted carries pinned: true); refused (409) for a phase outside a scoped run's onlyPhases, so a pin never widens it. | | phase.lane-reserved | journal | runner/queue-control.ts, runner/runner-control.ts, service-runs.ts | A person kept the next lane on this phase's scope for it (control-tower phase 100, #135 D.15–16): when lane {slug, phase} ends — or at once when none is named — nothing else on its scope, and nothing into the lane slot it frees, is admitted until it boards, even while it is parked or not yet queued; via: 'yield' when a yield made it. The scheduler holds it (Scheduler.reserveLane), re-registered from the record at a run's start and the console's open. Durable on queueControl.reserve, spent at the grant that boards it (scheduler.reservation-spent, phase.admitted laneReserved: true); refused for a phase outside a scoped run. | | phase.lane-unpinned | journal | runner/queue-control.ts, runner/runner-control.ts, service-runs.ts | A person lifted this phase's pin (control-tower phase 100); its run's other entries board in their own order again. | | phase.lane-unreserved | journal | runner/queue-control.ts, runner/runner-control.ts, service-runs.ts | A person lifted the lane kept for this phase (control-tower phase 100); the scope and the slot it held are free on the next scan. | | phase.lane-yielded | journal | runner/queue-control.ts, service-runs.ts | A person asked this phase's LIVE lane to yield (control-tower phase 100, #135 D.15): to {slug, phase} — the named phase, or the head of the queue — was given a reserved lane naming this one, so the moment this lane ends it goes there in the same scan and no sibling's re-board can take it (#134); steered says whether the session was asked to hand off at its next safe point (why when it could not be — no session of this console drives it). The actor with its reason; stored: true when the yielding run was not live here. | | phase.queue-bumped | journal | runner/queue-control.ts, runner/runner-control.ts, service-runs.ts | A person moved this phase to the front of its class (control-tower phase 96, #142; durable since phase 99, #135): the actor — by, via, origin, remoteUser, and reason when the press carried one — entryId when it was queued, and order {before, after} (0-based places in the scan) when it moved a waiting entry. The mark is on the phase's record (queueControl.bump, with a stamp that orders two bumps, the most recent first), so a re-created entry is born bumped and the run's own boarding order puts it first; it is spent when the phase is admitted. stored: true when the run was stopped and its record was edited. | | phase.queue-held | journal | runner/queue-control.ts, runner/runner-control.ts, service-runs.ts | A person held this phase in the queue (control-tower phase 99, #135 E.19): it stays queued with its age, is never admitted, and never reserves tokens — its entry waits on the held entry clock holder naming who and why — and its run boards it LAST, so the run's lanes go to its siblings first. The actor as on phase.queue-bumped. Durable on queueControl.hold until phase.queue-released. | | phase.queue-released | journal | runner/queue-control.ts, runner/runner-control.ts, service-runs.ts | A person lifted this phase's hold or deferral (control-tower phase 99): lifted names which (hold, defer). Releasing what is not held writes nothing. | | phase.queue-deferred | journal | runner/queue-control.ts, runner/runner-control.ts, service-runs.ts | A person deferred this phase until until (control-tower phase 99, #135 E.20): a hold that ends by itself — its entry waits on the deferred clock holder, whose leaseUntil the scheduler's defer timer wakes at — with the actor and its reason. Spent when the phase is admitted. | | phase.queue-withdrawn | journal | runner/queue-control.ts, runner/runner-control.ts, service-runs.ts | A person withdrew this phase from the queue (control-tower phase 99, #135 E.21): its waiting entry, if any, ends as AdmissionWithdrawn (its episode closes withdrawn, why: operator), the drive loop never boards it, the run boards its other phases instead, and a run left with nothing else parks naming it ("withdrawn from the queue by …") beside the Re-queue remedy. Durable on queueControl.withdrawn until phase.queue-requeued. | | phase.queue-requeued | journal | runner/queue-control.ts, runner/runner-control.ts, service-runs.ts | A person put a withdrawn phase back in the queue (control-tower phase 99): at its own seniority, or with position: 'front' — which also bumps it. | | phase.queue-ordered | journal | runner/queue-control.ts, runner/runner-control.ts, service-runs.ts | A person moved this phase in the Queue (one-pool phase 39, #380): the Queue's order press checked the move first and it can hold, so the override is on its record (queueControl.order: anchor — another plan's phase, first or last — and side), with the actor, its reason, the whole moves, the check's verdict, and the order before and after. Ranked at the bump key (an anchor in another plan at the class key); survives the entry, a retry, a relaunch and a restart; spent when the phase boards. | | phase.queue-order-reverted | journal | runner/queue-control.ts, runner/runner-control.ts, service-runs.ts | A person lifted this phase's Queue override back to policy (one-pool phase 39, the Queue's revert); lifted names the anchor and side it had. | | phase.queue-order-lapsed | journal | runner/queue-control.ts, service-runs.ts | This phase's Queue override lapsed to policy by itself (one-pool phase 39): its anchor finished, was skipped or withdrawn, or left the queue with its plan; why says which. Read whenever the forecast is. | | phase.queue-holder-changed | journal | runner/runner-control.ts | A queued phase's holders changed between scans (control-tower phase 60, #82): from and to name the old and new head (slug, phase, owner, kind), headClass the new head's class, and waitingOn the whole new set with each holder's phase-level ETA. The durable record.waitingOn follows it, and the lock-wait cap is armed or disarmed against the CURRENT set. | | phase.serial-behind | journal | analysis/timeline.ts, runner/runner-control.ts | A ready phase's scope meets a live lane of its OWN run in the same tree (control-tower phase 60, #64): it is serial work behind phase behind, not contention, so it never joins the queue — no phase.queued, no waitedMs, no queued badge — and reads ready (behind this run's P<n>) until that lane ends and it boards. Written once per change of the lane it is behind: by the drive loop before boarding, or by an admission whose holders became its own run's live lane (then after a phase.queue-closed withdrawn/serial). The timeline deliberately draws no bar for it (KNOWN_NO_BAR): ready time is not queueing. | | phase.reboard-requested | journal | runner/runner-attempt.ts, runner/runner-control.ts, runner/runner.ts, service-runs.ts | Something asked for this phase to be boarded again, naming the situation and the rung. | | phase.reconciled | journal | analysis/timeline.ts, converge.ts, runner/runner-base.ts, runner/runner.ts | A record was corrected against the board — the board wins over a stale checkpoint. The closed record's note says whose work the board may be reading: "closed outside this run" for a phase this run never started, "closed while checkpointed — the board reads done; not verified by this run" for one it did (autopilot-token-drain H7: a sibling lane of the same run may have finished it, and nothing ran its §Verification). closedBy: 'orphan' (5.2.0, #23) is the convergence loop's: a child that outlived its console ran to its exit criteria, so the run advances without a person's press even under resumeOnRestart: false — which was a question about resuming an INTERRUPTED session, and this one was not interrupted. The timeline ends the lane's bars here, like a terminal line (control-tower phase 61, #76). | | phase.recovery-cancelled | journal | runner/runner-control.ts | A recovery in flight was cancelled. | | phase.repair | journal | runner/runner-control.ts | A repair session (claude -p under the run) was boarded for a rung of the ladder. | | phase.repair-done | journal | runner/runner-control.ts | That repair session ended, and whether it fixed anything. | | phase.repair-script | journal | service-recovery.ts | The deterministic repair-artefacts.sh rung ran — the free first rung, no session. | | phase.repair-skipped | journal | runner/runner-control.ts | A repair was not boarded, and why (usually a freeze). | | phase.repair-stopped | journal | runner/runner-control.ts | A repair session was stopped before it settled. | | phase.replay-limit | journal | runner/runner-control.ts | A phase's session replay (run-<id>.p<N>.log.jsonl, one capped file per phase since control-tower phase 94, #133) crossed 80 % of its cap (state: 'near-full') or reached it (state: 'full'): bytes, cap (16 MiB) and file. Once per file — read from the file's last line, never from a writer's flag, so a re-drive does not say it again — and kept on the phase's record as replay, which the session pane shows. Past full the live view still streams and this journal keeps the record; the phases after it replay on their own files. A line with no phase is the run's own file (lines that name no phase). | | phase.resources | journal | runner/runner.ts | What this lane's process is costing — resident megabytes and percent of one CPU, from the ONE ps the liveness probe already takes. Written when either number moves and otherwise at most once a minute per lane, so a climbing session is reported as it climbs and a steady one costs one line a minute. Rounded to whole units: the third decimal of a CPU reading changes every tick and is a change nobody wants told. | | phase.resume | journal | analysis/timeline.ts, runner/runner-attempt.ts | The phase's own session was resumed with --resume under a raised cap (raise: turns or budget, the new maxTurns/budget and their sources). Since control-tower phase 46 (#61) also vehicle: resume — an instructed resume that spent its cap mid-work, carried on by closed() rather than halted no-handoff. | | phase.resume-at-boot | journal | retired 5.0.0 | Renamed phase.resume-automatic in 5.0.0. It named only the killed-lane resume, and only at boot in 15 of its 25 lines; the row stays so older journals still read. | | phase.resume-automatic | journal | converge.ts, runner/runner-control.ts, service-base.ts, service-recovery.ts, service-runs.ts | The console resumed this phase with no person in the loop — path says which of the ways (killed-lane, overdue-wait, rearm, system-stop, inbox-partial, watch-landed, since control-tower phase 5 wrapup — the session the console's own wrap-up notice reached declared partial --reason context|budget, as told, and boards fresh with the resume brief spending no ladder rung, trigger: outcome — and since control-tower phase 6 live-lane: a watch ref landed while this run's own runner was live, so Runner.landWatch re-opened the phase in place and the loop admits it ahead of its scope's queue with a reservation, trigger: watch and the landed ref, and no recovery door is called), trigger what woke it, and count how often this phase has been resumed automatically against MAX_BOOT_RESUMES. | | phase.resume-checkpoint | journal | runner/runner-attempt.ts | Which checkpointed transcript the resume attached to. | | phase.resume-done | journal | runner/runner-control.ts | A resume ended — cost (what was BOOKED for it, since control-tower phase 46), turns, last words. The session itself has its full ledger record beside it, as phase.session with mode: resume. It stamps no closeout marker any more: none ran. | | phase.resume-instruction | journal | runner/runner-control.ts | The instruction a resume was given, verbatim (capped). | | phase.resume-lost | journal | runner/runner-control.ts | A --resume that cannot reach its conversation: the CLI answered No conversation found, or the transcript could not be carried to the account paying. The session is marked gone on the record and the rung that tried it settles failed — never interrupted, which would offer the same resume again. | | phase.resume-policy | journal | runner/runner-control.ts | The resume gate's answer on whether a session is worth resuming (autopilot-token-drain phase 4, runner/usage.ts resumePolicy), asked of every --resume once the session is known not gone and not live, before any transcript port: choice resume or fresh, the reason — context-checkpoint (the console checkpointed it at 0.8 × its window), partial-budget / partial-context (it declared partial for that reason), account-changed or cache-cold (it ended at ≥ 250k tokens of context and the account paying is not the one that wrote its cache, or it last ran ≥ 55 min ago), and on the resume side cache-warm, small (under 250k) or unmeasured (no counters: a hand session, a record from before phase 3) — with sessionId, contextTokens and idleMs (null when never measured) and accountChanged. fresh boards the phase fresh with the resume brief rather than --resume it — the boarding, the in-place account switch, model-window retry and spent cap (phase.reboard-requested), a QA round (phase.qa-session fresh), the healer's rungs and a watch landing alike; an instructed resume ends instead, naming the reason, for the ladder's fresh rung. One line per decision about one run of a session. | | phase.resume-pressed | journal | service-runs.ts | A person pressed Resume (or Close out) on this phase (control-tower phase 96, #142): mode (resume · closeout), the instruction (capped at 500) and the actor, reason included when the press carried one. Written before anything acts, so a refusal further down cannot erase that it was asked, or why. | | phase.resume-refused | journal | runner/runner-control.ts, service-base.ts, service-runs.ts | A --resume was refused because the session it would resume is still running (why: session-live), or an unexpired lock names it (session-lease) — with its sessionId, pid and lock. A recorded refusal, never a halt: the resume happens once that session ends. | | phase.retry-ceiling | journal | runner/runner-base.ts | The CLI-side ceilings a session was spawned under, once per spawn: ceiling (CLAUDE_CODE_MAX_RETRIES) and bgWaitCeilingMs (CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS), each with whether the console set it or inherited it from the environment. | | phase.retry-context | journal | runner/runner-loop.ts | How much failure context was prepended to the next attempt's prompt. | | phase.retry-override | journal | runner/runner-loop.ts | A retry carried an operator's addendum or option overrides. | | phase.retry-requested | journal | runner/runner-control.ts | Something asked for a retry, with whatever it wants changed. Since 5.0.0 by: console with a reason marks the console's own re-board of a phase a credential wall parked, once a Continue's preflight proved the account can pay again (RCV-1). A person's Retry carries its reason too since (control-tower phase 96, #142) — the same field, the same question: why. | | phase.retry-storm-parked | journal | runner/runner.ts | Rung 2 of the retry-storm ladder: parked on the wall until its window, the record saying waiting on the usage reset so the classifier answers resource-wall:usage. | | phase.retry-storm-recycled | journal | runner/runner.ts | Rung 1 of the retry-storm ladder, on its OWN counter — a rung spent for silence is not a rung spent for a storm. | | phase.review-follow-up | journal | runner/runner-control.ts | A reviewer's requested changes were written into the phase as a follow-up. | | phase.review-held | journal | runner/runner-loop.ts | The phase is held by a reviewer's requested-changes, exactly as a person's would hold it. | | phase.review-session | journal | runner/runner-loop.ts | A reviewer session was boarded over this phase's diff (reviewEachPhase). | | phase.review-session-done | journal | runner/runner-loop.ts | The reviewer ended, with its verdict. | | phase.review-session-failed | journal | runner/runner-loop.ts | The reviewer session threw. | | phase.review-session-skipped | journal | runner/runner-loop.ts | No reviewer ran, and why. | | phase.ruling | journal | runner/runner-base.ts, service-runs.ts | A judgement the session recorded with phase-outcome.sh … ruling — what it decided and why. Never an outcome; nothing acts on it. | | phase.rung | journal | analysis/timeline.ts, runner/runner.ts, service-recovery.ts | A ladder rung was climbed: the situation it was chosen for, the rung, and the vehicle that drove it — signed with by from CLASSIFIED_BY like phase.situation, plus the healer's trigger. vehicle: card (since 5.0.0) is the widen-rule rung: a standing approval card, cardId naming it, offered by the runner's own climb or the healer; nothing spends until a person answers it. vehicle: runner with inline: true is a resource-wall rung the runner climbed at the wall itself (switch-account, wait-window with until); vehicle: timer is the wait-heal rung the healer accounted while a require MCP park's clock runs (until); vehicle: watch-clock is one watch pass run on demand. slot: 'person' with by: operator (control-tower phase 53, #56) is a person's press — a Retry, or a resume or closeout boarded fresh — accounted OUTSIDE the automatic ladder: it never reads a rung cap and never counts toward one, while its dollars still count. why on a switch-account rung (control-tower phase 78, #106) says why it resumed the run where it is — the run's own account reads no live wall now — instead of moving it. vehicle: runner WITHOUT inline (control-tower phase 79, #98) is the drive loop's own ladder climbing resource-wall:usage on a phase left pending: wait-window parks it on the wall's reset (until, why) in escalateLiveWall's shape — usageWall set, so a fresh reading, a spend or the re-probe lifts it sooner — and switch-account moves the run only onto an account with MORE room (phase.account-switch by: ladder) or resumes it in place. Since control-tower phase 135 (#212) a vehicle: card line names the permission ITEM the card is the Grant of (item): the widen rung raises one ledger item, and the card's inbox row folds into it. | | phase.rung-settled | journal | runner/runner-base.ts, runner/mcp-park.ts, service-recovery.ts | That rung's outcome — and, since 5.0.0 (RCV-6), the whole settlement: situation, params, costUsd (always a number; what the rung's own attempt spent, booked by chargeRung), note, turns/endedBy when known — and, since control-tower phase 5 (#36), cause (merit · environment · never-ran): an environment rung neither consumes itself nor spends a rung cap, up to five on one phase. Written by exactly two doors (Runner.settleOpenRung, Service.settleRungOn) plus the MCP flip's in-place settle, so the shape cannot vary; by: attempt-end marks the lane-end backstop that settles whatever an attempt left open, so no rung stays running past its attempt. A rung whose attempt the watchdog parked settles interrupted: the watchdog's act, never "the session declared" (control-tower phase 47, #52). | | phase.rung-unavailable | journal | analysis/timeline.ts, service-recovery.ts | A rung the ladder chose that THIS console cannot drive, and what it fell back to. | | phase.session | journal | analysis/timeline.ts, runner/runner-base.ts | One claude -p session ended, from the one spawn door every site goes through — ONE shape: mode (phase, resume, repair, qa, closeout, pr, review), endedBy (exit or who ended it) with endedReason, turns + turnsSource and costUsd + costSource (the stream's count for a turn that never closed; none when no cost ever arrived — costUsd is what the CLI REPORTED, its running total for the conversation, which a --resume carries forward), promptTurns (since control-tower phase 89, #62's SIZ-7: the largest ONE prompt's turns — turns sums every prompt's num_turns while the CLI enforces maxTurns per prompt, so this is the figure read against the cap and the one cap calibration samples; absent on a line from before it), bookedUsd (since control-tower phase 46, #62: what the console booked for this spawn — costUsd less the session's high-water mark, so a resumed conversation's earlier spend is booked once), subtype, isError, terminalReason, maxTurns/maxBudgetUsd each with its source — and, since control-tower phase 59 (#83), a measured cap's derivation (basis this-console or shipped, percentile, headroom, samples, the window's from/to, the observed value) — permissionDenials, argv, ms, said — the session's last words: its result, or, when the result was the CLI saying it could not reach the API, its own last prose, with that sentence kept beside it as transportError (control-tower phase 80, #108, lastWords). The timeline's working bar IS this line: it ends here and begins ms earlier, and a pr session draws none (control-tower phase 61, #76). | | phase.session-start | journal | runner/runner-loop.ts, service-recovery.ts | One of the automatic starts that does NOT go through startRun opened on this phase — the reviewer (auto-reviewer), the cloud review (ultrareview) or the ladder's pty agent (ladder-pty-agent) — with the actor whole (door, trigger, guard, counter) and the session mode. The run-level twin is run.start; the console-log twin for the MCP probe is mcp.probe.start (control-tower phase 100, #73). | | phase.shared-checkout | journal | runner/runner-loop.ts | The phase is working in a checkout something else also holds, naming the overlapping holders. | | phase.situation | journal | runner/runner.ts, service-recovery.ts | The classifier's answer for this phase: which situation, which sub-kind, and why — signed with by from CLASSIFIED_BY (drive · outcome · closed for the runner's own climb, heal for the convergence loop's healer, which adds the trigger that woke the pass and the evidence fingerprint it classified under). Since 5.0.0 the healer writes it ONCE per evidence: a pass that re-derives the same key from the same fingerprint writes nothing (RCV-9 — 1 332 lines, 1 249 unsigned, most of them "still parked"). | | phase.skills | journal | runner/runner-loop.ts | The skills injected into this phase's boot prompt. | | phase.skip | journal | analysis/timeline.ts, runner/runner-control.ts | The phase was skipped rather than run. Terminal for this boarding. | | phase.stall | journal | runner/runner.ts | A stall episode OPENED — one line per episode, never per tick. A wait's episode is its CHAIN (chain: {key, calls}, since the first call — control-tower phase 47, #67), so a second line comes only when a new chain begins or a local chain passes the local-job budget (overBudget). A silent episode whose lane's last output read as a person's turn carries suspectedStep (control-tower phase 44) and is followed by phase.human-step-suspected. | | phase.suspect | journal | runner/runner.ts | Three or more identical failing tool calls in a row on this lane — same tool, same command, same words back. A SUSPICION and never an action: nothing is signalled, nothing is parked, no ladder rung exists for it. Written once per (phase, attempt, call), with the evidence: which call, how many, and the tool-call ids to go and read. Pro. | | phase.stall-parked | journal | runner/runner.ts | The silent ladder is exhausted and the phase parked on an errand. | | phase.start | journal | analysis/timeline.ts, runner/runner-loop.ts | A boarding began: this phase now has a session, a lock and an attempt number. | | phase.steered | journal | runner/runner-control.ts | An instruction was written into the session's stdin — a steer, which outranks the plan for the rest of the phase. by, mark, instruction (capped at 500) and, since (control-tower phase 96, #142), the operator's reason when the steer carried one — the session is told the instruction, never the reason. | | phase.input-closed | journal | runner/runner.ts | A lane's live session can no longer be written to (control-tower phase 109, #170): its stdin closed. cause — turn-end (a turn ended with nothing of the operator's in flight and nothing of its own in the background: the ordinary end of a session), idle (the idle closer), epipe (a broken pipe), abort (the console stopped or checkpointed it) or background-ceiling (its own agents worked past the CLI's ten-minute ceiling with no new turn) — with pid and sessionId. The input is HELD open past a turn while the session's own agents or monitors work, so a steer, an ask, a peer's message and the wrap-up notice reach it; past this line each is refused naming the cause, and the lane view carries input: {open: false, closedAt, cause}. | | phase.stop-requested | journal | runner/runner-control.ts | Somebody asked this lane to stop, and who — the same actor shape the run-level stop carries (LFC-6). | | phase.stopped | journal | analysis/timeline.ts, runner/runner-control.ts | The lane stopped. Terminal for this boarding; before: admission when it was settled out of the queue, with the actor who asked. | | phase.switch-declined | journal | runner/runner-control.ts | A wall found no account to move to that carries the work past its own reset (control-tower phase 78, #100): from, until (when the wall being left resets — the horizon), declined — each account the switch picker passed over, with the window that would wall first (bucket, pct, resetsAt) and why — and wake, the soonest a walled pool member frees up when that is before until. The phase then waits on the soonest reset instead of moving work onto an account that would hold it for days, and is woken early to try the switch again when a pool member frees up first. Since control-tower phase 92 (#141, #100) the picker weighs each target at a MEASURED burn — its own plus the moving work's, read on the account being left — and also passes over an account whose usage is unknown (bucket: 'unknown', pct/resetsAt null: unknown usage is no headroom) or under the run's own minHeadroomPct in either window. | | phase.retry-kept | journal | runner/runner-base.ts | A PERSON's Retry was withdrawn with the queue before its lane spawned — a halt emptied it — and is kept open rather than settled withdrawn (control-tower phase 91, #131): rung, at, by. The record keeps the person's boarding hint, so the run that resumes boards it first; the service's heal sweep keeps it the same way (personRetryOwed). | | phase.admission-held | journal | runner/runner-loop.ts | A phase that waited for its scope was admitted, and a FRESH board read showed a dependency of it not done any more (control-tower phase 86, #136): after (the dependency), age (its queue seniority, kept). The grant goes back and the phase re-enters the queue behind that dependency — never withdrawn; it boards on the scan its dependency is done. | | phase.seniority | journal | runner/runner-loop.ts | A candidate boarded ahead of earlier ones by seniority (control-tower phase 86, #128 #132 #114): clock (operator for a person's re-board, else the seniority clock — queue age, hint time, an expired park), since, and ahead — the phases it moved past. Once per claim, so "why did P59 board before P80" has an answer. | | phase.lane-kept | journal | runner/runner-attempt.ts | A phase kept its lane — since one-pool phase 21 (#354 #295) at ANY partial that leaves its own WIP uncommitted in a shared tree, and at a waiting-external on its own detached job (reason: detached-verify, watch the cmd: probe) or over its own WIP; while it cannot board, its siblings in that tree are held (run.lane-held). Before that, only a wrap-up kept its lane (control-tower phase 109, #192): the session handed off partial --reason context|budget at the console's notice with paths of its OWN work uncommitted in a tree its siblings share (written inside its sessions' windows). Until it boards again it ranks right after a person's re-board (phase.seniority clock lane-kept), and its scope is held against every sibling whose scope meets it — in the very pass its lane was released in, when its attempt is still settling — so a sibling reads phase.serial-behind it rather than being admitted over its WIP. reason, paths, sessionId. Spent at boarding. | | phase.recovery-requeued | journal | runner/runner-control.ts | A recovery still waiting for its scope was given new words (control-tower phase 86, #149 ask 4): by, bytes. It keeps its queue entry — its place and its age — and boards with this instruction instead. | | phase.errand-tree | journal | runner/runner-control.ts | A checkout for a person's errand was prepared beside the run's own (control-tower phase 90): dir, by, adopted (an existing tree taken over), mounts (rel, sha, pushed). The errand card reads record.errandTree. | | phase.isolate-requested | journal | runner/runner-control.ts | A person gave ONE phase of a shared-checkout run its own worktree (isolate-phase, control-tower phase 90, #150): by. The phase boards in a lane worktree on pe/<slug>-p<N>, merged back when it settles, and no run-long branch hold reaches it. | | phase.tasks | journal | runner/runner.ts | The session's phase-tasks.sh list moved — totals and the active item. | | phase.tokens | journal | runner/runner-base.ts | One session's API calls, folded, as it ended (autopilot-token-drain phase 3, runner/usage.ts) — from the spawn door phase.session is written at, for every mode, and only when the session made a call: calls, lastContext and peakContext, firstContext (since control-tower phase 59: the FIRST call's context — for a fresh session its boot, what the sizing model's per-repository boot floor is the median of), the sums input, cacheWrite, cacheRead and output, and rebuilds — calls that wrote at least max(100k, half the context) instead of reading it; a fresh session's first call is its build, never a rebuild. With them sessionId, attempt, resumed, model and the window it was judged against (null when no model was known), and for the phase's own sessions the status checks the poll-loop guard counted during it (pollCalls, pollDenied), and the account it was spawned under (default for the machine login) — whose prompt cache it wrote, which the resume policy compares with the account paying (phase.resume-policy). The same entry, with endedAt, is kept on the phase record as tokens[] (newest last, twenty at most). | | phase.tool-auto-granted | journal | retired 5.0.0 | Renamed phase.approval-auto-granted in 5.0.0, beside the two other card twins; the row stays so older journals still read. | | phase.tool-deferred | journal | runner/runner-base.ts | A session's turn ended on a defer: its result carried deferred_tool_use (toolUseId, tool), the call kept for a claude -p --resume to run (spike S3). The relay says defer only when the console is going away with a question open. | | phase.tool-denied | journal | service.ts | A tool call this console refused. rule names the line — a policy.deny rule, in-turn-wait for the guard that refuses waiting inside a turn, poll-loop for the guard that refuses a run of status checks (phase.poll-loop), or sign-in for the guard that refuses an interactive sign-in a -p session could never answer (control-tower phase 44 — with shape, the SIGN_IN_SHAPES member it started with, and step, the human-step kind the refusal told the session to declare; the command is redacted), run-tree-clone for a git clone / gh repo clone into a run worktree (control-tower phase 90, #139), run-tree-detach for a git switch --detach / git checkout <commit> of the run's OWN checkout, which would get the next boarding refused (control-tower phase 112, #183), gate-forge for the guard that refuses a session setting the console's own gate door (PE_GATE_DOOR) or writing a plan's gate-status.md with the file tools (control-tower phase 107, #174 — with why), or console-forge for the guard that refuses a session pressing its own console — a route or CLI verb in door-model.js's AUTHORITY_ROUTES (answering a card, editing the policy, a run's settings, a gate, /api/write, checking or dismissing a person's step), reached by an HTTP client, the CLI or a wrapper's text, or a write into the console's state or config directories (control-tower phase 129, #218 — with verb, the row's name or console-files, why, and path for a file) — and, since 5.0.0, command carries what was refused (bounded). The same denial is stamped on the phase record (toolDenied), where the situation classifier reads a deny-list rule as blocked-declared:permission above any prose (LFC-3) and the errand quotes it. A poll-loop, sign-in, gate-forge or console-forge denial is the exception: journal only, never on the record, because none of them is a wall a person widens from a card. Since control-tower phase 135 (#212) EVERY one of these — a deny rule, a deny by shape and each guard — is also kept as a wall on the lane's record (PhaseRecord.walls, newest last, at most eight, cleared at a boarding): the evidence a blocked --needs permission declaration must cite (G5). A guard's wall is never — no grant through any door. Since one-pool phase 24 (#260) in-turn-wait also refuses a wait loop whose pgrep -f/pkill -f pattern matches its own command line (selfMatch: true, matched the probe), with the pid and sentinel forms that end (selfMatchReason). | | phase.tool-refused | journal | runner/runner.ts | The words a session read when the CLI refused one of its tool calls — a tool result paired to a denial, or in the CLI's own "Claude requested permissions to …" sentence. tool, toolUseId, detail, and since 5.0.0 target — what was refused, as the denial ledger names it (TRS-8). Never an interrupted call's "the tool use was rejected". | | phase.tools | journal | runner/runner.ts | Which tools the session was given at boot. | | phase.transcript-port | journal | runner/runner-attempt.ts, runner/runner-control.ts | A resume asked for the transcript under the paying account's config directory: ported (bytes moved), findable (the resume will find it), why and cliVersion — the same four phase.account-switch carries. A findable: false line is followed by a fresh boot, never a resume into nothing. | | phase.verify | journal | analysis/timeline.ts, runner/runner-attempt.ts | §Verification finished. Closes the bar phase.awaiting-verification opened. Since control-tower phase 40 it carries trees: {repo, branch, head, role} of every repository the commands could read — the one they ran in, the scoped ones, any a command reached with cd or git -C, and every other one under the run's root — absent on runs recorded before it. On the automatic path — no phase.awaiting-verification before it — the timeline's verifying bar is measured back from this line by the sum of its commands' ms (control-tower phase 61, #76). | | phase.verify-baseline | journal | analysis/timeline.ts, runner/runner-attempt.ts | The phase's §Verification BASELINE, taken at its first boarding on the tree it boards on (control-tower phase 83, #103): the working tree and head it describes, how many lines were reused and how many were measured (run then, every line on its own), and the red ones. Once per phase per run; a red already here is inherited at the verdict, never charged to the phase. Since control-tower phase 105 (#190): a line is reused from ANY plan's ledger of this console when a run on the same tree, command, environment digest and directory is under a day old — reusedFrom names each one's slug, run, phase and ageMs; redOnce lists the lines measured red, run once and never retried; concurrent says it ran BESIDE the session in a clean checkout of the boarding head (false: measured before boarding, where git would not export), told whether the session heard it as a next-turn note (true, 'queued' until its first handle, false), and loadWaitMs how long its commands waited under the machine-load guard — since one-pool phase 26 a heavy line's wait for its seat in the machine's heavy-job budget (phase.verify-queued), a light line's for the load as before. A baseline that throws writes failed and concurrent instead. | | phase.verify-command | journal | service-runs.ts | One verification command run from the console's terminal, with its exit code. | | phase.verify-disagreed | journal | analysis/timeline.ts, runner/runner-attempt.ts | A command the phase's session recorded (phase-outcome.sh … verified) and the console then ran itself came back with the other verdict (control-tower phase 62, #68): command, the session's {code, tree, at, session}, the console's {code, tree}, and — when the console ran it because the tree had moved — why and the changed paths. The audit's one unexplained number (a suite green in the sessions and red in 15 of 17 console runs) is a line naming both trees from here on. | | phase.verify-in-missing | journal | runner/runner-attempt.ts | The phase's **Verify in:** resolves outside the repository root, so the root was used instead. | | phase.verify-in-mount | journal | runner/runner-attempt.ts | A phase's Verify in: said . (or nothing) while its lines are judged at a run's MIRROR root that is no git tree — a plain directory holding the mounts (one-pool phase 20, #311). With exactly one mount, that mount is the plan's tree, so §Verification and Setup run in dir there: a yarn install no longer lands in the mirror root, and a ! grep no longer passes over a tree it cannot see. Once per phase. | | phase.verify-inherited | journal | analysis/timeline.ts, runner/runner-attempt.ts | A verdict's reds split by the phase's baseline and attributed (control-tower phase 83, #103): inherited — the failing tests (or whole commands, when the output named none) that are not the phase's own, each group with the phase that owners it and how that was found (charged: a phase was failed for it and no run since was clean of it; wip: the only difference from a tree the line was clean of it on is uncommitted paths another phase's session wrote, named in paths; commit: the only phase whose sessions committed between the last run clean of it and the head it was first seen red on — several are candidates) — and own, the phase's own reds. Since the fifth amendment a red absent from the baseline is attributed too (it leaves the phase only when the phase made none of the commits in its range), and a red && chain is split into its members, each run alone, each share naming its chain. Only the own reds fail the phase; with none, it settles done and the inherited ones ride its record. | | phase.verify-overtaken | journal | runner/runner-attempt.ts | §Verification was red and the BOARD says the phase is done — the board overtook it. Since control-tower phase 62 (#68) only for a red no command BROKE (a fragment nobody could run); a command red on its final attempt re-opens the phase instead (phase.verification-failed). | | phase.verify-owed | journal | analysis/timeline.ts, runner/runner-attempt.ts | Journalled against the OWNER of reds another phase's verification inherited (control-tower phase 83, #103, the fifth amendment): by — the phase that found them — and reds, each {command, chain?, failures, how, paths?}. The same reds are kept on the owner's record as owed, until its own line runs green. Only a red with ONE owner is owed; candidates owe nothing on a guess. | | phase.verify-in-place | journal | analysis/timeline.ts, runner/runner-attempt.ts | A §Verification (or a baseline) that should have run on a clean checkout of the phase's HEAD — the working tree held changes that are not the phase's own — ran in place instead, because git refused the checkout (control-tower phase 89): reason (the paths and whose), refused (why: a superproject, whose checkout would hold none of its submodules, or git's own error) and head. When the checkout is made, phase.verify carries export: {head, repo, reason, paths, owners} instead. Since control-tower phase 105 a baseline asks for a checkout of the boarding head every time, to run beside the session; git refusing it writes this line with reason "the baseline is measured before boarding". Since control-tower phase 106 (#191) a checkout of a repository that is a superproject's submodule stands at its own path with the superproject's other entries — the sibling repositories, the root's files — linked around it, and the paths that trigger one are narrower: an untracked path no phase's session wrote, anything inside node_modules/.venv/venv, and an untracked path the phase's own Setup: names never count; a line naming a sibling the checkout cannot provide is environment, never red. | | phase.wait-cut | journal | service-runs.ts | A person's Continue cut a waiting phase's window short (control-tower phase 111, #179): parker — watchdog for the console's own park, else who declared the wait, whose ref landed when the press asked it (read) — by and the window it was. The loop then admits the phase as an expired wait and resumes its own session; it is never parked again on the window that was cut. | | phase.wip-gate | journal | analysis/timeline.ts, runner/runner-attempt.ts | The wrap-up's fast gate (control-tower phase 89, #127): after a partial --reason context|budget declared at the console's wrap-up notice, the console ran the phase's fast lines (its §Verification lines measured under five minutes, within ten in all) on the COMMITTED work-in-progress — on a clean checkout of HEAD when the tree held anything uncommitted (exported). sha, lines (how many ran), ms, and either ok: true (with cleared, the sha of a wipRed it retired) or ok: false with red ({command, failures?} per red line) and files (what the phase committed since it first boarded) — the same facts written to the record as wipRed, which the boarding order, every sibling's brief and the verification's attribution read. skipped says why nothing ran (no line measured fast; git could not name the commit; the gate itself failed). | | phase.agents-killed | journal | runner/runner-attempt.ts | A session's process ended with its own background subagents or monitors still running, and the CLI's ceiling stopped them mid-work (control-tower phase 109, #188): agents (id, description), handedOffAt (the session's declaration, when it declared one) and paths — the uncommitted paths written after it. Kept on the record as agentsKilled and named in the next boarding's brief (or the in-attempt retry's), each agent's newest words included, so the next session does not take those paths for its predecessor's handed-off WIP. The Stop hook now refuses a partial or complete exit while one runs (hook.stop-background), so this line means one got through it. | | phase.verify-preflight | journal | runner/runner-attempt.ts | The boarding preflight's warnings about the §Verification it is about to trust. | | phase.verify-preflight-parked | journal | analysis/timeline.ts, runner/runner-loop.ts | The preflight refused to board on a §Verification it could not run. | | phase.verify-queued | journal | runner/runner-attempt.ts | A HEAVY §Verification or baseline line — a suite, a build, a gate (heavyCommand) — is waiting for a seat in the machine's heavy-job budget (one-pool phase 26, #327): purpose (baseline or verify), command (folded), reason (budget: every seat taken, holders naming whose — any console's, through <stateHome>/fleet/jobs/; or load: the machine is busy and the line is a baseline, which waits on the load for at most 30 minutes), running, budget (one per seven cores, at least one) and the sentence the queue page shows. Written once per wait; the line then RUNS — scheduled, never skipped. A phase's own finishing §Verification is urgent: ahead of every baseline and past the load, never past the budget. | | phase.verify-dequeued | journal | runner/runner-attempt.ts | The line that waited (phase.verify-queued) took its seat and runs: purpose, command, waitedMs, and pastLoad — true when a baseline's load wait ran out and it ran on a still-busy machine (one-pool phase 26, #327). | | phase.verify-proven | journal | analysis/timeline.ts, runner/runner-attempt.ts | What the console made of the proofs the phase's session recorded before it ran §Verification (control-tower phase 62, #68): proven — each command it did NOT run, because the session proved it green at an equivalent tree (the same tree, or one where only paperwork changed: docs/handoffs/**, .locks/**, CHANGELOG.md — listed as paperwork) — and refused, each recorded proof it ran anyway, with why (a red proof, a tree that moved, a tree this repository does not hold). tree is the working tree about to be verified. Written only when a proof was on file. | | phase.verify-stopped | journal | runner/runner-attempt.ts | The run was stopped mid-verification; what ran, and what never did. | | phase.verify-timeout | journal | analysis/timeline.ts, runner/runner-attempt.ts | A §Verification command ran past its clock on its last attempt — the console retried it itself at twice the limit and it was cut again (control-tower phase 83, #95): the commands, the limits each attempt ran under, retried, the reason and the trees compared. Not a red verdict and never charged to the streak: the phase and the run park verify-timeout for a person, who raises the phase's Verify timeout: (or the plan's) or finds the hang, then Re-checks. | | phase.run-tree-config | journal | runner/runner-base.ts | A session — of any mode the spawn door starts — left git configuration in its trees that could make the console's own git run a command (one-pool phase 2, #272): the guarded keys that are new since the session spawned (git-guard.ts GUARDED_KEYS — a hook path, an fsmonitor, a driver, a credential helper, an include, an alias), the trees they are in, the entries as read and the session's mode. The lane halts run-tree-config before any verification or landing git runs there; a person removes the key (or confirms it is theirs) and presses Retry. | | phase.run-tree-locked | journal | runner/runner-base.ts | An index.lock no git command is writing blocks a run tree (one-pool phase 20, #328): older than INDEX_LOCK_ORPHAN_MS (2 min — a git command holds it for seconds), found by the spawn door before a session boards (stage: boarding, the session's mode) or before §Verification runs (stage: verification), each with its path, tree, mtime and ageMinutes. The lane halts run-tree-locked with the paths named rather than wedging every index write there; the lock is never removed by the console — a person checks no git of theirs is mid-write, removes it and presses Retry. | | phase.reverify-after-restart | journal | runner/runner-loop.ts | A §Verification the console's going-down cut — phase.verify-stopped, or a checkpoint that still read verifying at boot — is run again before anything else is decided about the phase (control-tower phase 48, #69): no session, the same three checks an attempt's end runs, settled by their verdict. Carries cutAt, cause (shutdown · stop · crash), what had ran and what had not (notRun), and the status the record read (was). Until this line reconcile never closes the record on the board's word, and a run is never called finished over it. | | phase.verify-unanswered | journal | runner/runner.ts | A verification fragment needed a person and nobody answered. | | phase.verify-unrunnable | journal | runner/runner-attempt.ts | Every verification command's lead is missing from the PATH here — a fact about the MACHINE, never a verdict about the work. | | phase.verification-failed | journal | analysis/timeline.ts, runner/runner-attempt.ts | §Verification's FINAL verdict was red over a phase whose handoff reads complete, and the phase was RE-OPENED (control-tower phase 62, #68) rather than recorded done with a red verdict on it: reopened: true, times (how often a verdict re-opened it), the failed commands, reason and the trees compared. The record reads failed with reopened set: reconcile leaves it alone, the run is never called finished over it, its direct dependents are held (phase.verification-held), and the ladder gives it one fix rung (verify-red:reopened) before its errand. A green verification of it is what closes it — phase.done then carries reopened. | | phase.verification-held | journal | analysis/timeline.ts, runner/runner-loop.ts | A ready phase was held because a direct dependency was RE-OPENED on a red final verdict (control-tower phase 62, #68): held names the holders. Written once per change of holder; the held phase stays outstanding and boards the tick its dependency verifies green. | | phase.widen-decided | journal | runner/runner.ts, service-recovery.ts | A person answered a widen-rule card (TRS-10): decision, by, the rule and the cardId. Allow strikes that deny rule for the plan and resumes the phase's own session; deny (or the card's clock, by: timeout) settles the rung failed and leaves the errand. | | phase.verify-waived | journal | runner/runner-attempt.ts, runner/runner.ts | A verification was waived rather than asked: the plan states no §Verification and allowUnverifiedPhases is on (stage: preflight boards, stage: verify passes on the handoff), or — since phase 11 — the phase's Person-check: allow waived its prose checks by policy (by: policy, decisionKey, source, the checks in notRun), or — since 2026-09-18 — the operator set checks aside at the start door (by: launch, the checks in notRun; a phase whose every check was set aside boards and passes on its handoff). Since one-pool phase 21 (#267) two more shapes: an operator waived ONE line mid-run (POST /api/run/:slug/verify-waive; by the person, line, fp, reason, rulingId — the waiver is also a deviation ruling on the plan's ledger), and the verification that honoured such waivers (stage: verify, by: operator, lines — each {line, by, reason, rulingId}; the line is set aside under the verdict's waived, never run). | | phase.budget-approaching | journal | runner/runner-base.ts | A budget reached its warning line, BUDGET_WARN_PCT (80%) of it gone, and was announced under budget (control-tower phase 14, #40): budget (wait, phase-usd, run-usd), limit, spent, left and unit — the arithmetic the push opens on — and the key it was claimed under (the phase's attempt and the limit; run:<limit> for the run's dollars), so it is said once per budget per attempt and again only after a raise moves the limit. A wait says it at the GRANT, when the window it was given will carry the phase past the line (crossesAt), while there is still time to raise it; the dollars say it as a session's spend is booked. | | phase.wait-budget-spent | journal | runner/runner-loop.ts, service-base.ts, service-runs.ts | A declared wait met a SPENT wait budget and the phase now waits on its refs with no clock of its own and a budgets errand (control-tower phase 45, #59) — never failed, never a streak charge, never a rung, and a landing still resumes its session. ledger says which allowance ran out (the hours, the count of declared waits, or the console's own parks), refusal states the arithmetic, watch the refs still polled (a cmd: one past the budget's end), parked: true, by whose declaration it was. Written by the runner at park time and when a park ends over its budget, by the inbox for a hand session's declaration, and by the overdue-wait ruling for a stored park whose clock went by while nothing ran (with the trigger that asked — boot at a console start). | | phase.wait-settled | journal | runner/state.ts | A waiting record's clock was ruled on at load (WAI-6): to: rearmed — the run had lost its waitUntil and took the record's parkedUntil; to: pending — nothing would ever fire the clock (why: operator-stopped or clock-unarmed, the run's status named), so the record is boardable again with its declaration intact; to: interrupted — the run is over (why: run-over). clock is the parkedUntil ruled on, lateByMs how far past it was. Said ONCE per clock since control-tower phase 50 (#53): a stale copy written back over a settled record is settled again, and the line is not repeated. | | phase.wait-resume | journal | analysis/timeline.ts, runner/runner-loop.ts | A parked phase is boarding again — cause says why it is back (declared-window, budget-elapsed, watchdog, since control-tower phase 6 landed for a watch ref that landed while the run was live — its prompt says the ref LANDED — or console-park for a park with no declaration), with parkedMs from the park's own stamps, lateMs past its clock, the budget left and the turn cap's source — remaining since control-tower phase 46 (#61): a wait-resume continues the phase and runs under the phase's turn cap (the size row until control-tower phase 59; the measured cap since) less the turns the phase already spent, never under RESUME_MIN_TURNS (it was closeout, 60). | | phase.waiting | journal | analysis/timeline.ts, runner/runner-attempt.ts, service-recovery.ts, service-runs.ts | The phase parked with a clock, answered by the wait budget: requested against granted, capped (only ever a default window), the budget with its source and what is left, by (the session, a hand session, the console's watchdog — or, since 5.0.0, ladder: a wait-window, poll-park or timed-park rung the healer climbed on a stopped run, with rung, wait and until, bounded by the rung caps rather than the declared budget) and any refs nothing can poll. Since 5.0.0 a session's ref-less wait after a guard denial carries the ref the console minted for it in watch (phase.watch-missing says where it came from). Since control-tower phase 87 (#122, #126) declared: blocked with rung: poll-park and vehicle: runner is the runner's own park of a blocked declaration naming refs the watch clock can poll: the same budget and window as a waiting-external park (a declared window past the budget is capped at what is left, and the window its cmd: refs are paced in ends there too), no ladder rung accounted, no streak asked — it used to defer to a healer that acts only on a stopped run, and settle failed. unbudgeted: true is a park on refs read from this console's own state alone (phase:, verify:), granted as asked and never summed into the budget. | | phase.watch-already-landed | journal | declared-probe.ts | A declaration's ref had ALREADY landed when the session declared it (control-tower phase 50, #86): the console asked the staged declaration's refs at ingest (POST /hooks/declaration, 20 s in all), nothing was parked, and phase-outcome.sh exited 3 so the session carried on. ref, detail, the declared status, writtenAt, sessionId and every ref's answer. Written on the run the staged file names, else the plan's latest run. | | phase.turn-refused | journal | declared-probe.ts, runner/runner-attempt.ts, runner/runner-control.ts | The guard at the door of Your turn refused a declared person's turn (control-tower phase 130, #207): rule (G1 a reason the kind does not allow, G2 no proof, G4 the AI can do it itself — every guide command this run's policy allows, or reach with no --tried, G6 a secret), exit (2, or 4 for G4), sentence and the commands G4 named. At the pre-check (stage: door) nothing parked and phase-outcome.sh exited with exit; at ingest no item was raised and the park's note says why. Never a value from the declaration. Since control-tower phase 135 (#212) rule: G5 too (exit 4): a blocked --needs permission declaration that cites no wall this console recorded for its lane — "nothing refused this — run it". | | phase.watch-checked | journal | watch-scheduler.ts | A watch ref was probed and its state changed. Written on TRANSITION only. | | phase.watch-landed | journal | service-recovery.ts | A watch ref landed. Written once per REF (watchLandedJournalledFor is a per-ref set since 5.0.0 — two refs on one phase used to overwrite each other's stamp and re-journal every minute), on the declaration it answers. resumed: false with why (control-tower phase 87, #126) is a landing on a phase the live run reads failed: nothing in the run resumes it by itself, so the landing is said here and becomes ONE phase.errand naming it and "Press Retry", instead of being retired in silence; its row is then kept terminal on the record (watchLandedDone) and never offered again. | | phase.watch-refused | journal | runner/runner-control.ts, runner/runner.ts, service-runs.ts, watch-scheduler.ts | The policy judged a cmd: ref and will judge it identically for ever, so it is journalled once and dropped from the rotation. Terminal for that ref. Since control-tower phase 39 (#42) it is also written at INGEST, why: own-lock, by the runner's outcome file, its live twin and the unsupervised inbox: a lock: ref naming the declaring phase's own lock, which only its own closeout releases, with reason the shared refusal sentence (OWN_LOCK_WATCH_REFUSAL); and by the watch clock for such a ref armed before the screen existed, which it retires rather than lands. | | phase.watch-missing | journal | runner/runner-attempt.ts, service.ts | A waiting-external declared with NO --watch ref after the console's in-turn-wait guard refused the session's own wait — the guard's recipe followed without its last step (TRS-3; measured: declared 37 s after the refusal, resumed 581 min late). source: hook is the hook seeing the ref-less declaration on its way past; source: declaration is the runner ruling on it — adopted names the ref it minted from the refused command (from: denial) or the plan's Waits on: refs (from: plan), and adopted: null means neither existed and the declaration was refused (phase.declaration-refused). | | phase.watch-unpollable | journal | runner/runner-attempt.ts, service-runs.ts | A declared watch ref no scheme can parse — named with the reason, because nothing will ever probe it and a silent clock is not what the session asked for. | | phase.wrapup-undelivered | journal | runner/runner.ts | The context wrap-up notice (phase.context-wrapup, 0.6 × the window) did NOT reach the phase's session (control-tower phase 46, #79): the steer's own refusal as reason, the attempt it failed on, context, window, sessionId. The notice is tried again on every usage event until it arrives — then phase.context-wrapup is written with the attempt that delivered it — or the 0.8 × checkpoint fires. One line per refusal REASON, not per API call; the record keeps the count (contextWrapup.attempts). | | phase.worktree | journal | runner/runner-loop.ts | A lane worktree was created for this phase, and on which branch. | | phase.worktree-adopted | journal | runner/runner-control.ts | An existing lane worktree was picked up rather than created. | | phase.worktree-failed | journal | runner/runner-loop.ts | The lane worktree could not be created; the phase degrades to the shared checkout. | | phase.worktree-landing | journal | runner/runner-loop.ts | The lane's merge-back is ABOUT to run — written before the merge so a console killed between the two leaves a record that the landing was attempted. Its phase.worktree-landed sibling is the outcome. | | phase.worktree-landed | journal | runner/runner-loop.ts | The lane branch was merged back into the run branch. | | phase.scope-drift | journal | runner/runner-loop.ts | The phase committed in a repository its plan's Repos cell does not name — the repo, the phase's DECLARED scope (its Repos tokens plus the docs root's docs/handoffs/<slug>), the commit range, and the commits CREDITED to it; excluded counts what was left out and why (phase-lock, other-plan, declared, not-ours). Since control-tower phase 63 a commit is credited only when it is on the lane's branch or its own session printed it, and never when it is a lock commit, another plan's, or inside the declared scope — so a line means the phase itself left its scope. Detection only: admission, the lock and every concurrent plan were told this repository would not be touched. | | phase.worktree-resynced | journal | runner/runner-loop.ts | The lane worktree was brought up to date with the run branch. | | policy.advisory | log | service-base.ts | Once per boot: the permission policy in force cannot do its job — ask-empty (the merged ask list is empty, so no profile asks and Guarded and Trusted are one posture) or deny-struck (a shipped deny rule is struck out of the wall that holds with the console dead) — with the rules it names and whether the operator has acknowledged exactly those. Announced on the health channel until acknowledged; GET /api/policy carries it as advisory. | | policy.advisory-acknowledged | log | service-base.ts | The operator recorded having read a standing advisory (Settings ▸ Permissions), against the rule set it named — a changed set stands again. | | policy.advisory-unread | log | service-base.ts | The boot-time policy read threw, so no advisory could be judged; the policy page still reads on demand. | | policy.changed | both | service.ts | One of this console's policy answers (policy.<key>, Settings ▸ Automation ▸ Policy answers, or a ruling remembered on this console) moved — the key, the word it held, the word it holds now and who changed it. Logged on the console and noted on every live run's journal, since the answer in force is what each run's next park reads. | | policy.edited | journal | service-live.ts, service-recovery.ts | The approval policy was edited through the console, with what was added and removed. A widen-rule card approved on a stopped run writes it onto that run's journal too (the live-runner note cannot reach a run nobody drives). | | policy.updated | log | runner/approvals.ts | The policy file on disk was rewritten — the counts, and by whom. | | run.account-env-failed | journal | runner/runner-control.ts | Building the environment for an account threw; the run continues on the default. | | run.account-env-refused | journal | runner/runner-control.ts | The console REFUSED to compose a session's environment for the run's account (one-pool phase 5) — an API key that is missing, or a provider no recipe in this build runs: {accountId, reason}. Unlike run.account-env-failed the session is not started on the machine login: the phase halts with the reason. | | run.account-env-missing | journal | runner/runner-control.ts | An account's stored environment is not on this machine. | | run.account-cooling | journal | runner/runner-control.ts | A run LEFT an account over a usage window and the account was marked machine-wide before any switch (ACT-5, SES-2): id, the hashed orgId, the reason, by (classifier, live-wall, preflight), the breaker's state after the write (cooling, or entitled for a per-model window), until (the window's reset, or ACCOUNT_COOLDOWN_MS when none parsed), the wall written (bucket, resetsAt) when the CLI named the window. rankAccounts does not answer the account until until, whichever console asks. | | run.account-retired | journal | runner/runner-control.ts | A credential-class refusal RETIRED the run's account: id, the hashed orgId, reason, by, class. Since 5.2.0 the ORGANISATION's row is written only for an org-scoped class (ORG_SCOPED_CLASSES: org-policy, billing) AND a structured verdict the API returned — a prose match retires this credential and nothing else, because a half-hour outage once retired 7 accounts across 2 organisations from one transient stop. certificate never retires at all: it takes the corroboration path (accounts.certificate.corroborated) and cools one credential. Nothing automatic reopens a retirement except proof against a certificate one (accounts.retired.reopened); otherwise a person clears it (POST /api/accounts/:id/clear-retired). | | run.failure-charged | journal | runner/runner-control.ts | chargeFailure() added a phase to the failure streak (control-tower phase 45): its ending's cause is a merit failure (MERIT_FAILURE_CAUSES) and the phase was not already counted. Carries consecutiveFailures (the set's size, on the wire), max, cause, phases (the ordered set, oldest first — what a failure-streak halt's sentence names) and the reason. The line a replay re-derives the streak from. root (control-tower phase 87, #122) is the ROOT CAUSE the charge was blamed on — commit:<sha> (the blamed commit: a sibling's red WIP, phase 89's wipRed) or ref:<refs> (the refs a block watched) — kept beside the phase so a second charge on it is held, and named in the streak halt's sentence ("phase 5 (on commit 1c8164e9)"). | | run.failure-streak-held | journal | runner/runner-control.ts | chargeFailure() REFUSED to charge a failure, and why: the ending's cause is not a merit failure (work on disk with no handoff, the network), the phase is already in the streak (a second ending of one phase is one failure — control-tower phase 45), the run is already halted on a refused credential, or the counter is at its maximum. Carries consecutiveFailures, max, why, cause, phases and the reason it declined to count. The measured shape it makes visible: a run reading 3 of a maximum 2, over its own ceiling, because one outage charged three failures in 39 seconds (#37). Since control-tower phase 87 (#122) two more: declared-wait is not a merit failure (a block naming a ref the watch clock can end — the runner parks those, so it is a belt-and-braces word), and a charge on a ROOT CAUSE a counted phase already carries is held with root and sameAs (that phase) — the measured P50 and P41 were one sibling's WIP, charged 1 and 2 of 4. | | run.account-switch | journal | runner/runner-control.ts, service-runs.ts | The run was asked to move to another account, and by whom — the actor derived from the request (ACT-11: twenty of twenty-one lifetime switches read by: console because the route defaulted what the client never sent). Since 5.0.0 the account left is recorded through the one helper too (lastLeftAt in the learned store) — held against nobody: a person may be moving TO a fuller quota. Since 5.2.0 the STORED branch writes it too (#22): a move onto a checkpoint no loop was driving used to set accountId silently, leaving a record whose accounts[] named something else and whose history explained neither. checkpointed: null there — not zero: the live branch counts the lanes it held a pid for, and a count of none would claim it looked. Since control-tower phase 53 (#56) the live branch adds the target to the run's accounts (its pool) and saves at once, so the switch survives the loop's next write; a target the quota door refuses (retired, a learned wall, a spent window) never reaches this line — the route answers 409 before anything is checkpointed. Since control-tower phase 78: rekeyed counts the queued entries that moved with it (#92); deferred names the live lanes it did NOT checkpoint — a lane only closing out (its handoff reads complete, or its session has declared its outcome) finishes where it is (#107) — and when is now or boundary (the verb's when: 'boundary' checkpoints no live lane at all). A PERSON's switch is remembered on the run as accountChoice (#106). | | run.account-switch-reverted | journal | runner/runner-control.ts, service-runs.ts | A switch moved the run back onto the account a PERSON had moved it away from (control-tower phase 78, #106): from, to, choice (the person's switch — accountId, from, at, by, kept as RunState.accountChoice), by and reason. Only ever under a live wall on the person's choice — the switch-account rung resumes in place on any account with room, and the runner moves only at a wall — and announced under limits, so a person's choice is never undone in silence. | | run.account-switch-back | journal | runner/runner-control.ts | The run moved back onto the account an AUTOMATIC switch had taken it off, once that account's wall had reset (control-tower phase 92, #100): from, to, resetsAt (the origin's reset, as recorded at the move), movedAt and phase (the boarding it moved at). Taken at the next boarding after the reset — the lanes' boundary — or at a wall on the account it was moved to, past the horizon rule, which would weigh a window that just reset against the new account's days-off wall. A wall wait on the new account wakes at the origin's reset. A person's switch clears what is owed, and is never undone by this. | | run.identity | journal | runner/runner-control.ts | The run bound to WHO its account answers (control-tower phase 91, #131): account, key (the facade's identity digest — credential, organisation, email), email, org and reason — start (once, at the first start), switch (a move to another account) or accepted. Kept as RunState.identity; every boarding compares the account's identity now against it. | | run.identity-changed | journal | runner/runner-control.ts, service-runs.ts | The account a run is bound to now answers somebody else — the machine login was signed in as another person, a profile re-logged (#131): account, was and now (account, key, email, org), phase when a boarding found it, by: 'service' when the service parked a run no loop drives. The run parks — halt kind identity-changed, press-only — with an errand offering both ways on; nothing boards on the new identity and no heal resumes it. | | run.identity-accepted | journal | service-runs.ts | A person answered an identity park (#131): choice (continue on the new login, or move to a registered account that answers the identity the run started on), was, now, moved {from, to} for a move, by and via. The run is bound to now and resumed. | | run.superseded | journal | service-runs.ts | A stopped run a later run of its plan replaced (turn-by-turn phase 7, runner/supersede.ts): by (the successor's id), reason ("superseded by run "), halt (its halt kind), status, onlyPhases when the successor is scoped. Written once, by the read-path resolver, when it resolves the run auto: true with supersededBy; unresolve puts it back for good (reopenedAt). Its asks leave Your turn with it — the handled log's console row reads this line. Never written for a live, unlanded, plan-halted, reopened or dismissed run. | | run.isolate-requested | journal | runner/runner-control.ts | A person asked a SHARED-checkout run for a checkout of its own at its next boundary (control-tower phase 90, #150): by, at: 'next-boundary'. Applied by the loop's first fill with no lane of the run live. | | run.run-requested | journal | runner/runner-base.ts, service-runs.ts | A phase asked the RUN for an act only it can take (turn-by-turn phase 6, #286): act (one of RUN_ACTS — isolate, switch-account, raise-budget, stop, hold), compat (true when the console READ it from a permission declaration naming no rule or command, on a shared checkout whose lane is not on pe/<slug> — the compatibility rule), askedAs (the session's own --needs word then), reason, sessionId. One line per declaration; the item is ONE approval per run and act, every declaring phase a waiter. The Pro supervisor's run-requested detection reads it. | | run.run-request-decided | journal | service-recovery.ts | A run request's approval was answered (turn-by-turn phase 6): act, answer (approve — the act was taken through the run verb of its name — or deny), by, door, phases (every phase that asked, each resumed told the answer), stepId, kind. The supervisor's detection of the request ends here. | | run.isolation-switched | journal | runner/runner-loop.ts | The boundary switch took: the run works in a checkout of its own from here on (phase 90, #150) — dir, branch, by, asked. | | run.isolation-switch-refused | journal | runner/runner-loop.ts | The boundary switch could not take a checkout (phase 90, #150): by, refusal (the preamble's word), reason. The run stays in the shared checkout, as it was — the switch was an escape offered, and failing it does not stop a run that was working. | | run.checkout-rebuilt | journal | runner/runner-loop.ts | An isolated run's checkout did not stand at a boundary and was rebuilt before the next boarding (control-tower phase 90, #123 #139): dir, was (the checkout word before), reason. | | run.mount-quarantined | journal | runner/runner-loop.ts | Foreign content at a mount of the run's mirror was moved aside rather than deleted (control-tower phase 90, #123 #139): mount, from, to (under stale-mounts/<ts>/), kind, branch, dirty, unpushed, confirmed (a person's Repair checkout press — by — for content that was dirty or unpushed). One line per mount. | | run.account-switched | journal | runner/runner-control.ts, service-recovery.ts | It did — at preflight, and reason says at which door: auth (the run's login would not sign in) or, since 5.0.0, quota (its window was spent, a wall was learned, or the credential is retired — ACT-2). tried names the candidates that could not take it; the account left is marked first (run.account-cooling). at: ladder (5.0.0) is the healer's switch-account rung on a STOPPED run: the run moved to account from from and relaunched, reason the situation key. pool (control-tower phase 53, #55) names the run's declared accounts when it has them: every failover — preflight, the live limit switch, the healer's rung — ranks inside the pool and never leaves it by itself. | | run.adopt.alive | journal | runner/runner-control.ts | On boot the console found its own children still running and adopted them. | | run.adopt.interrupted | journal | runner/runner-control.ts | A child from a previous console is gone; its phase is marked interrupted. | | run.agent-artefacts | journal | runner/runner-control.ts | What a session left behind — repos touched, branches, worktrees. Registered, never deleted. Since one-pool phase 20 (#262) only what the phase could have made: a tree under any console-managed root (every run's worktrees/, the staging tree) and a pe/<slug> or pe/<slug>-p<N> branch of a plan this instance runs are the console's and are left out; a tree outside the phase's own lane is listed under appeared (maker unknown) rather than worktrees; words says each in a sentence. | | run.anchor-classify-failed | log | service-recovery.ts | The classifier threw while choosing the phase an action should anchor to. | | run.auto-recovery | both | service-recovery.ts | The convergence loop chose a rung for a stopped run. | | run.admission-refused | journal | runner/runner-loop.ts | The boarding door refused a phase because the run's account is retired in the machine-wide breaker (reason: account-retired, RCV-1) — the credential was refused since the run started, under this run or another — and the run halts credential-refused without a session spent. | | run.auto-recovery-failed | log | service-recovery.ts | That attempt threw; the rung is settled failed. | | run.auto-recover-skipped | journal | retired 3.0.0 | Written 1.6.0 to 2.x by the pre-convergence auto-recovery: the healer looked at a stopped run and declined to act, with the reason. The convergence loop (3.0.0) replaced it with run.converge, whose kind: skip rows carry the same fact; the hub's journals still hold 65 of these lines. | | run.base-branch | journal | runner/runner-loop.ts | The base this run's branch is cut from, resolved to a commit ONCE per drive: asked is the word (origin/HEAD, head, a ref), declaredBy is who said it (plan · run — the launch form's own word, phase 15 · console · default), and ref/sha/source are what it resolved to — source being origin-head, trunk, head or ref, because "we forked from main" means four different things. A word that resolved to nothing carries resolved: false and the run forks the way it always did. | | run.branch-mismatch | journal | runner/runner-loop.ts | The plan names one branch and the run stands on another. | | run.branch-reseated | journal | runner/runner-loop.ts | A run branch a squash-merge had landed was moved onto what landed, in the run's own checkout (control-tower phase 112, #183): mount, branch, from (the pre-squash tip, kept in the reflog), to, into (origin/<trunk>), by: content, the merge tree and the proof command. Only at a boundary or before the console's own verification, with no other lane live, on a clean mount standing on the branch or detached at a commit the trunk reaches — a mount a session detached on origin/main is re-seated rather than switched back. | | run.budget-raised | journal | runner/runner-control.ts, service-recovery.ts, service-runs.ts | The run's USD budget was raised, by how much, and against what cap — inline at the wall by the runner, or (5.0.0) by the healer's raise-budget rung on a halted run, by and rung saying so; the budget halt dissolves and the run relaunches. Since control-tower phase 14 (#40) every raise also carries ONE shape, {budget, was, now, by, via}: the runner's own auto-raise writes budget: run-usd, by: runner, via: auto-raise beside its from/to/pct/cap; an operator's POST /api/run/:slug/raise-budget writes the budget it raised (wait, phase-usd, run-usd, ladder), was/now in its unit (minutes, dollars, rungs), unit, the setting a run setting was patched under or the wait's scope (phase — its Waits on: max — or plan), what the raise answered (cleared: the spent stamp, the errand, the budget halt), stored: true on a run with no loop behind it, and the derived actor (by, origin, remoteUser, reason; the request's own transport as channel, since via names the verb). |

| phase.landing | journal | pro/landing/engine.ts | The landing policy engine took the phase: its resolved policy, gitlink and conflict words, and the repositories (with the branch each lands from, null for one the console does not own). Written once per landing, before any step. | | phase.landing-degraded | journal | pro/landing/engine.ts | A pr/trunk policy fell to hold (from, to, reason): the phase worked on a branch the console does not own — a shared checkout, no lane — so a pull request from it would carry whatever else is there (F28). The run's own settle opens the PR. | | phase.landing-pushed | journal | pro/landing/engine.ts | The console pushed the phase's branch (repo, ref, sha, flag — new · fast-forward · up-to-date, remote). The one publication the console makes; pushRef in runner/worktree.ts. | | phase.landing-push-refused | journal | pro/landing/engine.ts | The push did not happen: reason is not-allowed (no --allow-publish, or the plan's permission.destructive row names no git push), ref-not-pe, refspec, no-remote, rejected (the remote moved — never retried, never forced) or failed. The landing parks; not-allowed resumes from the push on the next drive. Since control-tower phase 135 (#212) a not-allowed refusal is also a capability wall on the phase's record and raises ONE permission item whose Grant is at the machine only (the unit verb, then a restart when idle — never from a device). | | phase.landing-session | journal | pro/landing/engine.ts | The landing session was boarded (repo, ref, base, stackedOn when the base is a dependency's open pull request, cwd) with the numbered prompt: open the pull request, record the row, stop. | | phase.landing-session-done | journal | pro/landing/engine.ts | That session ended — ok, cost, turns, what it said — or could not be spawned (reason, error). | | phase.landing-recorded | journal | pro/landing/engine.ts | The ledger row was VERIFIED against gh pr view (ok: true, the PR's URL and number, state) — or refused (ok: false, reason: no-row, session-refused, unexpected-state, no-pr-url, gh-unavailable, head-mismatch, not-merged, sha-mismatch). Also written when the watch clock reports the PR MERGED (state: pr-merged, sha). | | phase.landing-resumed | journal | pro/landing/engine.ts | A restart continued an unfinished landing from a step (local, push, session, verify), re-deriving every step from facts — a push already on the remote is not repeated. | | phase.landing-conflict | journal | pro/landing/engine.ts | A landing's merge conflicted and the plan's Conflicts: word (policy) was applied. Two shapes: an integrate landing against the staging branch (repo, branch, into, files, detail — the merge was aborted, the row reads conflict and an errand names the files), and a LANE that would not merge into the run branch (branch, into, files, detail, others — the lanes that wrote the same branch — structural, with kinds when true, and the action taken: halt for the default word and for every structural conflict, park for a phase parked landing-conflict with the run driving on, rebase-session when the phase's own session was resumed once to merge and re-land; a rebase that conflicts again writes a second line with action: park). | | phase.landing-failed | journal | runner/runner-loop.ts | The engine itself threw in one half (local, remote, resume) — the error, bounded. The phase is untouched; the landing is left where it was. |

| phase.landing-threw | journal | runner/runner-loop.ts | The settle at the end of a lane THREW — the merge-back, the local landing or the tree-hold note; the measured case is a rebase-session whose resume could not start (control-tower phase 82, #117). error is the message. Written before the lane gives its grant back, deletes itself and persists — all of which now run however the settle ended — and the throw is then re-raised so the lane's catch halts phase-crashed as before. The phase is never left running: rebaseLane restores its word in a finally. |

| phase.landing-kept | journal | pro/landing/engine.ts | The hold settle (or a pr/trunk degraded to hold) found the ledger already holding this repository FURTHER along than held — a session's own pr-merged or landed, a pushed — and left the row (one-pool phase 21, #249 #240): a row only moves forward (LANDING_ORDER). repo, kept (the row's state, which landing.repos takes), refused (held), sha, pr, by. phase-landing.sh refuses the move back too, unless --allow-backward. | | phase.landing-record-failed | journal | pro/landing/engine.ts | phase-landing.sh refused or failed a ledger write (repo, state, detail). The record keeps its own copy; the ledger is behind it until a person records the row. | | run.integration-landed | journal | pro/landing/engine.ts | An integrate landing merged one repository's branch into its staging tree (repo, branch, into, at, kind merged · empty, commits). Nothing was pushed; pe/integration is never pushed in 5.1. | | run.staging-locked | journal | pro/landing/engine.ts | The staging tree carries a lock this console did not write (by) — a person resolving a conflict by hand, another console — so the integrate landing parked rather than merging; retried on the next drive. | | run.clash-zone | journal | service-base.ts | Two branches of this repository both touch a clash zone — a lockfile, a migrations directory, .gitmodules, a generated file: files that merge CLEANLY and are wrong afterwards. pair, zones and the radar's state. Written once per (repository, pair, zone) on each run the pair involves, and never a refusal: it is the moment serializing the two is still cheap. | | phase.radar-hold | journal | runner/runner-control.ts | The phase queued behind a radar holder (many-plans-one-repo phase 9): the repository's radar holds a conflicted pair between this phase's branch and another live phase's, the landing order puts the other first, and Settings ▸ Automation's radarSerialize is on — so this one waits until the other lands rather than racing it to the merge. pair (the two refs and the files), other (slug, phase, runId, branch) and the order of both. Off by default in 5.1: with the preference off the pair is advisory and this line is never written. |

| phase.lint-unrun | journal | runner/runner-attempt.ts | The plan's lint could not RUN after the phase completed (why: crashed — the engine died on a signal or in the signal range, or answered with no verdict line — or timeout), with the summary that says so. NOT a verdict: the phase stands, the streak is untouched and the queue is not withdrawn, because nothing was learned about the plan. Before 6.0 a crash read as a failing plan and halted the run after every completed phase with a blank where the reason goes (#17). | | phase.lint-sibling-skipped | journal | runner/runner-attempt.ts | The plan's lint failed after the phase completed, and EVERY problem it counted was a sibling's handoff whose lock is live and whose "What this phase did" is still the template (siblings, the issues lines, the verdict summary) — that lane's moment between its scaffold and its body, which its own finish lints again. Not charged, not a halt (#225, one-pool phase 22): before it, a sibling's half-written handoff charged the finishing phase plan-lint, marked its green verification failed and halted the run. One problem of any other kind and the plan-lint halt stands. | | run.console-shutdown | journal | analysis/timeline.ts, runner/runner.ts, service-base.ts | The console is going down, on the run's OWN journal (SHD-8): intent says why — shutdown (a Shut down press, with its mode), restart, or signal (a SIGTERM/SIGINT no request explains) — and reason in words (the press's sentence, or the signal's name), live says whether a runner of this console was driving it (with lanes, each {phase, pid, sessionId}, pids and phases), and waitUntil/clock/discards name the clock the exit throws away. Written for EVERY run the shutdown abandons — queued, waiting, paused or parked on a clock — not only a driven one, so a parked plan learns from its own journal that the console went away. | | run.queue-reordered | journal | runner/queue-control.ts, runner/runner-control.ts, service-runs.ts | A person set this run's phases in an order (control-tower phase 99, #135 B.9): phases, the list — each bumped with a stamp one behind the phase before it, so the listed phases board first, in that order, ahead of the unbumped queue, and the list outlives every entry — with the actor and its reason, and before/after, the run's waiting phases in scan order around the change. One line for the list; the phases carry the marks. | | run.restart-requested | journal | service-base.ts | A restart or an update was asked from inside one of this run's own sessions — the CLI sends $PE_SESSION_ID — so it is that session's act, written on its run at its phase (control-tower phase 48, #69): the actor, the session, and whether it was to update, wait (whenIdle) or force. |

| run.restart-held | journal | service-base.ts | A restart waiting for its lanes holds this run's plan — the plan's scope meets theirs — so none of its phases boards until the restart (control-tower phase 48, #70). Carries who asked (by), since, the expectedEnd from the lanes' own estimates, the capAt past which it restarts anyway, and how many lanes it waits for — the wait's own record, where the queue's accounting used to find nothing. | | run.restart-released | journal | service-base.ts | The restart hold on this run ended: how — drained (the lanes finished), capped (the wait reached its cap and the restart went ahead over the lanes still live, which carry on after it) or cancelled — and waitedMs. |

| run.cost-repriced | journal | service-base.ts | A stored run brought to the booked-delta cost model at boot, ONCE (control-tower phase 46, #62, CC-6/CC-7): until then each spawn's reported total_cost_usd was booked whole, and a --resume reports the conversation's running total. Re-booked from the run's own phase.session lines — only the re-reported part comes off, money no line explains is kept — with before and after (spentUsd), the phases whose figure moved (each {before, after}), the sessions read and the costModel stamped (2). Written only when the figure moved; the stamp alone makes a second boot a no-op. | | run.clocks-remeasured | journal | service-base.ts | A stored run brought to the every-session clock model at boot, ONCE (control-tower phase 58, #66, EE-2): until then only a phase's own attempts opened a window, so every resume, repair, QA round, closeout, landing and review worked the phase and left no time on its record. Each such phase.session line of the run's journal became a closed window [time − ms, time] — never one the spawn door had already opened — with the phases whose worked time moved (each {before, after}, ms), the sessions added and the clockModel stamped (2). Written only when a figure moved; the stamp alone makes a second boot a no-op. | | run.converge | journal | converge.ts | The convergence loop acted on this run — which action, and what triggered the sweep. | | run.converge-failed | journal | converge.ts | That convergence action threw; the run is left as it was. | | run.closed-plan | journal | converge.ts | The run's plan is closed (complete, superseded or abandoned): convergence stopped it where it stood and pinned it, once. It is never woken, re-armed or re-boarded while the plan stays closed. | | run.engine-busy | journal | runner/runner-control.ts | The engine timed out reading the plan, and the drive loop is waiting it out rather than stopping (control-tower phase 81, #104): attempt (1-based, in a row), delayMs and until from the back-off series (ENGINE_BUSY_BACKOFF_MS — 15 s, 30 s, 1 min, 2 min, then 5 min, up to ENGINE_BUSY_MAX_WAITS = 8 waits, about 24 minutes), the engine's error, and the machine's load (1-minute load average) and cpus. The run reads waiting on engine-busy; no failure is charged, no halt is written and no rung climbs. A timeout that outlasts the whole series halts plan-unreadable, saying how many reads timed out. | | run.engine-busy-cleared | journal | runner/runner-loop.ts | The first board read that answered after one or more engine timeouts (control-tower phase 81, #104): waits is how many back-off waits it took. The drive loop carries on from that read. | | run.errand | journal | converge.ts, runner/runner-attempt.ts, runner/runner-control.ts, runner/runner-loop.ts | Something a person must settle, raised against the run rather than one phase. Since 5.0.0 the credential wall writes its errand here at the halt (RCV-1): resource-wall:auth, said quoting the session's sign-off, by: runner; the healer defers to it rather than writing a phase-level twin. | | run.heal-pass | log | service-recovery.ts | One healer pass over a run's open phases finished: the trigger that woke it, how many candidates it classified, how many situation lines it journalled against how many it found unchanged (RCV-9), how many shellOutsSaved the pass's shared evidence cache answered instead of git, and which phase it chose to climb (null when none). | | run.heal-dropped | log | service-recovery.ts | A healer pass was dropped because the run moved on while it read (control-tower phase 110, #178): live again, a phase holding a newer attempt than the pass read, the run in flight again, or its stop resolved — why says which, with trigger. Nothing was written or launched on the old reading: what a pass changes is MERGED onto the record as it stands, never its copy saved back, and converge keeps no fingerprint (run.converge carries stale: true). | | run.manifest-override | journal | runner/runner-control.ts | The start door was refused on blocking manifest rows and a person started the run anyway: which rows (rows) and who (by). The one recorded way past the prelude; written right after run.start. | | run.readopt-held | log | service-base.ts | Re-adoption was skipped because this console holds its automation (SHD-5, FLT-9): kind is stopped (a Shut down with "stay off" left its marker) or autostart-off (the machine profile says this console does not start its work unattended), with who and when. The release runs the pass again. | | run.progress-tick-failed | log | runner/runner.ts | The three-second run:progress tick threw. The run is untouched — a heartbeat must never be able to stop one — and the surfaces that ride it simply do not move until the next tick that does not. | | run.resume-asked | journal | converge.ts, service-base.ts | A console restart stopped this run and resumeAtBoot is ask, so nothing was relaunched and the operator is asked on the next app load. | | run.resume-refused | log | service-base.ts | An overdue wait was not resumed because the session that declared it is still running; the phase is re-checked shortly. | | run.failure-streak-reset | journal | runner/runner-control.ts, service-runs.ts | The consecutive-failure counter was zeroed: was (what it had been) and, from the operator verb, by (who) and via (clear-streak). Two writers: a person's press of Start/Continue, which has always restored the budget, and — since 5.2.0 — POST /api/run/:slug/clear-streak, which zeroes the count and NOTHING else. That one is its own verb because when an outage spends the budget there is no phase to retry, and Retry was the only thing that cleared it. Since (control-tower phase 96, #142) also reason, the operator's why, when the Clear streak press carried one. | | run.finished | journal | analysis/timeline.ts, runner/runner-loop.ts | The run ended. Its timeline axis stops here. | | run.freeze-escalate-frozen | log | service.ts | An escalation was due and the console itself is frozen, so nothing was escalated. | | run.freeze-escalated | both | runner/runner-control.ts, service.ts | A frozen lane's escalation clock ran out and the freeze was escalated. | | run.freeze-standing | journal | runner/runner-control.ts | A lane's escalation clock is held because the console was frozen under it. | | run.frozen | journal | analysis/timeline.ts, runner/runner-control.ts | The run was frozen (SIGSTOP). Silence after this is the feature, not a fault. | | run.frozen-idle | journal | runner/runner-loop.ts | The drive loop found the run frozen with nothing to do and stood down. | | run.git-radar | journal | runner/runner.ts | A branch/worktree pair changed state under the run. | | run.git-strategy-overridden | journal | runner/runner-control.ts | A run started with a git strategy that does not honour lines the plan's §Session budget or its phases state — a **Branch:** it will not create, Worktrees: on it cannot grant, Checkout: bullets it leaves inert (control-tower phase 11, #18). lines names each (kind, plan, run); ack is the launch's answer (override, or automatic from a door no person pressed). Sessions are then told the concrete branch instead of being asked to record a discrepancy. | | run.branches-pruned | journal | runner/runner-loop.ts | Merged pe/* branches were deleted (-d only) once the trunk contained them. | | run.halt | journal | analysis/timeline.ts, runner/runner.ts | The run stopped. kind is a RUN-level halt kind; a kindless halt stays run-level. credential-refused (since 5.0.0, RCV-1) is the API refusing the run's own credential — an organisation policy, an expired login, a billing hold, a certificate — with the account retired for its organisation and one run.errand beside it. The timeline takes every lane in flight down from here, with halted: <kind> as the cause, until the next run.start (control-tower phase 61, #76) — as do run.parked, run.paused, run.stop-requested, run.console-shutdown and run.waiting-external, each with its own cause. | | run.halt-classify-failed | log | service-runs.ts | The classifier threw while reading a halt. | | run.halt-cleared | journal | runner/runner-control.ts | A recovery that is driving cleared a stop its own board read disproved (control-tower phase 81, #105): a plan-unreadable halt (kind, and its reason) over a plan the engine reads again (why). The halt is kept through a recovery on purpose, so a crashed one does not lose why the run stopped; this is the one stop the recovery's own read answers. | | run.halt-retracted | log | service-live.ts | A halt stopped being true and was withdrawn. | | run.halt-withdrew | journal | runner/runner-control.ts | A halt took the run's queued lanes out of the admission line: entries withdrawn from the scheduler, phases reset to pending. Nothing granted is touched — a lane in flight keeps its scope until it ends. | | run.halt-superseded | journal | runner/runner-loop.ts | The board overtook the halt while the lanes were still draining. | | run.orphaned | journal | runner/state.ts | A read of the run found a session in flight whose LAUNCHING console is gone and parked the run orphaned-session (control-tower phase 110, #175): pids and phases of the live children, launcher — gone (the console recorded at the launch, its pid and boot instant, is no longer the live one) or unknown (an older record named none) — and frozen when some sit under SIGSTOP. A child the live console launched is never orphaned, however quiet its record or whatever a reader was told about which runs are live; the park used to be written to the record alone, and the hourly census made it once an hour about runs being driven. | | run.park-lifted | journal | runner/runner-control.ts | A park a timed-out approval card raised lifted because the phase the card was about completed (control-tower phase 97, #140): approvalId, reason (the park's own sentence), why. The run goes back to driving and boards the next ready phase; a park no card raised is never lifted this way. | | run.held | journal | runner/runner-control.ts, service-runs.ts | The run's queue is holding: admitted nothing new, by whose hand. by, and since (control-tower phase 96, #142) reason when the Hold carried one; also written for a stored run (service-runs.ts, stored: true and the actor) — holding a run before Start used to leave no line at all. | | run.isolation | journal | runner/runner-loop.ts | A worktree lane was REFUSED, with the refusal class; the run degrades to a shared checkout. | | run.isolation-kept | journal | runner/runner-loop.ts | A refusal was overridden and the worktree kept. | | run.isolation-adopted | journal | runner/runner-loop.ts | This run took over a PREVIOUS run of the same plan's kept integration/, which was still standing on the run branch. A kept tree is kept because it held work, so it is adopted rather than reclaimed or rebuilt; from names the run directory it belonged to. | | run.isolation-detached | journal | runner/runner-loop.ts | The run's checkout owns no branch: detached at the default branch's head, with the sha it stands at. |

| repo.radar-failed | log | service-base.ts | The repository conflict radar could not measure — a git that declined, a root that went away. The card shows no rows; nothing downstream of the radar is load-bearing, so this never fails a run. |

| runner.scope-drift-probe | log | runner/runner-loop.ts | The scope-drift comparison threw. Never fails the phase — the worst case is the silence this probe replaced. | | run.isolation-reclaim-failed | log | runner/runner-loop.ts | The reclaim threw. Never fails the run — the build simply refuses branch-in-use. | | run.isolation-reclaimed | journal | runner/runner-loop.ts | A CLEAN checkout sitting on the run branch was switched to the default branch, so the run can take it. | | run.lane-held | journal | runner/runner-loop.ts | A candidate was held back because a phase of the same run KEEPS its lane (one-pool phase 21, #354 #295): the holder (holder) left its uncommitted WIP in the shared tree at a partial, or is waiting on its own detached job (reason: detached-verify, watch its cmd: probe), and cannot board yet (holderStatus pending, queued or waiting). The candidate's claim meets the holder's in the same tree, so it waits; one in a worktree of its own boards. reason, paths, since. Once per holder, sibling and keep. | | run.landing-proof | journal | runner/runner-loop.ts | Whether a run's work is on its trunk once every phase is done (control-tower phase 112, #184): landed, and one row per repository it owns a branch in — each mirror mount, the root's included, or its one repository — with mount, tip, into (origin/<trunk>; the local trunk only with no remote copy), state (contained by ancestry, content or merge-commit — a squash the trunk has moved past, at the trunk commit that held the branch, one-pool phase 21, #336 — ahead, conflicted, unknown), files and fetched (fetched/failed: since one-pool phase 21, #370, origin/<trunk> is fetched before the proof — the one fetch the console runs, PROOF_FETCH_ARGV; a failed fetch proves against the last one and says so in detail). The proof is tree containment asked in each repository: git merge-tree --write-tree <trunk> <tip> writing <trunk>^{tree}, never git cherry. Anything not contained parks the run unlanded with one errand; a scoped run proves it only when it completed the whole board. | | run.mirror-branches-restored | journal | runner/runner-base.ts | The mirror branches a final phase's §Verification had settled were put back (control-tower phase 62, #47) — each pe/<slug> re-created at the commit its mount stands on, and the mount switched onto it — because a session was about to be spawned into the mirror (cause: spawn) or the verdict did not close the phase (cause: not-green). Carries phase, branch, restored, and failed with a reason for any mount left detached. | | run.mirror-branches-settled | journal | runner/runner-attempt.ts | Before the run's LAST phase ran §Verification, the run's own pe/<slug> was deleted from every mount where it held nothing the trunk lacks — a clean tree, and the branch reachable from the trunk (0 commits ahead) or, since control-tower phase 89, merging into it without changing a path (a squash-merged pull request, proved by merge-tree, against main or origin/main) — and the mount detached at the commit it stood on (control-tower phase 62, #47), so a check of the checkout's hygiene does not read the run's own idle branches as litter. The ROOT mount is settled like the others since phase 89, refused by the same rule when it carries a landing the trunk lacks. Carries phase, branch, settled, proofs (since phase 89: per settled mount, by — ancestry or content — into — the trunk ref that holds it — and tip, the commit the branch named, which re-creates it) and kept (a candidate left standing, with why). Undone before any later session (run.mirror-branches-restored). | | run.mirror-drifted | journal | runner/runner-loop.ts | One or more of a mirror's mounts was standing off the run branch — git submodule update inside a root mount detaches its siblings — with the mounts that drifted and the ones git switch put back. A mount holding uncommitted work is never moved, and the detail says so. | | run.mirror-grown | journal | runner/runner-loop.ts | A standing MIRROR grew at a boundary, no lane live, because the plan's Repos cells now name a repository it did not mount (one-pool phase 20, #356): the added mounts and the mounts it holds now, built by the drive preamble's own scope-grew path. Growing takes no new slot of the worktree cap. Before this a phase added mid-drive boarded into a tree without its repository. | | run.mirror-grow-refused | journal | runner/runner-loop.ts | A mirror could not grow to mount what the plan now names (#356) — the repositories it is lacking, the mounts it holds and the preamble's refusal when it named one. The run parks isolation-refused with the repositories named; nothing boards into a tree without its repository. | | run.mirror-repaired | journal | runner/runner-loop.ts | A mirror mount had GONE — its directory or its registration — when the run came back to its checkout (control-tower phase 82, #94): missing (the mount paths), created (those rebuilt), ok, detail when the rebuild failed, dir and branch. The manifest beside the mirror names every mount, so the repair is ensureMirror over it — a standing mount adopted, a lost one added back on the run branch — instead of the degrade to the shared checkout that followed before. | | run.limit-paused | journal | analysis/timeline.ts, runner/runner-control.ts, runner/runner.ts | A usage wall paused the run, from the stream or from a corpse. | | run.limit-resume | both | analysis/timeline.ts, service-base.ts | A wait clock fired and the run is being resumed — on the run's journal too, with lateByMs and whether the resume was ruled on as overdue. | | run.limit-resume-failed | log | service-base.ts | That resume threw; the run stays paused on the wall. | | run.limit-resume-frozen | log | service-base.ts | The resume was due and the console is frozen, so it did not happen. | | run.limit-resume-held | log | service-base.ts | A wait clock fired on a run it may not resume by itself — the operator stopped it, it is resolved, or its usage wall pauses for a person — so nothing started, and the log says which. | | run.lock-debris-released | journal | converge.ts | The convergence loop released a lock whose holder is provably gone. | | run.mcp-continue | journal | service-runs.ts | Phases parked on MCP policy: require were released to continue. | | run.park-withdrew | journal | runner/runner-control.ts | A park took the run's queued lanes out of the admission line: entries withdrawn from the scheduler, phases reset to pending so a resume picks them up again. One of three withdrawal records (pause, halt, park) that a template literal used to compose. | | run.parked | journal | analysis/timeline.ts, runner/runner-control.ts, runner/runner-loop.ts | The run parked — waiting on something rather than stopped by something. Since control-tower phase 112 (#184) a run whose every phase is done parks here when its work is not on its trunk: outstanding: [], unlanded naming each repository's branch, state, target and files, and the halt kind unlanded. | | run.pause-cancelled | journal | runner/runner-control.ts | A requested pause was taken back. | | run.pause-withdrew | journal | runner/runner-control.ts | A pause took the run's queued lanes out of the admission line: entries withdrawn from the scheduler, phases reset to pending, so what was never tried stays startable when the run continues. | | run.pause-requested | journal | runner/runner-control.ts | A pause was requested, after which phase, and by which actor (by, via, origin, remoteUser). The actor carries reason since (control-tower phase 96, #142), when the Pause carried one. | | run.paused | journal | analysis/timeline.ts, runner/runner-loop.ts | The run actually paused, after the phase the request named. | | run.permission-profile | journal | runner/runner-control.ts | The run's permission profile was changed mid-run, and by whom — or by a resume that named a different one (control-tower phase 77), beside that resume's run.resume-settings. | | run.permission-prompts-skipped | journal | runner/runner-base.ts | This relay: off run's sessions go without --permission-prompts none because the CLI is known to predate the flag (version below floor, 2.1.259), which it would reject as an unknown option (QRL-9). Once per run. An unknown version still gets the flag. | | run.plan-recover | journal | service-runs.ts | A deterministic plan-repair step ran over the run's artefacts. step: 'waiting' is the press that found a session from an earlier console still working in the run (5.2.0, #21 §2) — pid, phase and session named, nothing changed and no errand written, because the run continues by itself when that session ends. step: 'superseded' (control-tower phase 45, #45) is the press that found the run's halt anchored on a phase the board shows done — phase and the halt's kind named — and cleared it, so the run continues in the same press instead of answering "nothing to launch" over a halted run with ready phases. Since control-tower phase 81 (#97) a stop about the PLAN (plan-lint, plan-unreadable), or one already marked resolved, is answered by the plan's lint before anything else: step: 'lint-clean' (with the halt's kind) cleared it and the run continues; step: 'lint-red' kept a plan stop standing, wrote the plan-broken:lint errand and quoted validate.sh's summary; step: 'lint-unrun' relaunched nothing because the lint could not run (why: timeout, crashed or no-plan). | | run.pr-pending | journal | runner/runner-loop.ts | Every phase is done and the work branch still owes a pull request. | | run.pr-session-skipped | journal | runner/runner-loop.ts | The PR session did not run — usually a freeze. | | run.preflight-refused | journal | runner/runner-control.ts | The run's own preflight refused to start it, and what was tried. wall (since 5.0.0) names which door: auth or quota — the quota door used to throw an exception the automatic callers swallowed into a log line; it is a parked run with one run.errand now (ACT-2). pool (control-tower phase 53, #55): the pool it stayed inside, which the errand names too. | | run.push-carve-out | journal | runner/runner-control.ts | The new-branch push carve-out was switched, and by whom. | | run.readopt-failed | log | service-base.ts | Re-adopting a previous console's runs threw. | | run.readopt-frozen | log | service-base.ts | Re-adoption was skipped because the fleet is frozen. | | run.readopt-queued | log | service-base.ts | A queued run from a previous console was picked up. | | run.readopt-wait-held | log | service-base.ts | Boot re-adoption found a wait it would not arm or rule on: pinned by the operator, resolved, or an errand the convergence loop writes. | | run.rearmed-freeze | log | service.ts | A freeze escalation clock was re-armed after a restart. | | run.rearmed-wait | both | service-base.ts | A park's clock was re-armed — armedBy says whether a restart did it; a restart's re-arm goes on the run's journal as well, with how long until it fires. | | run.relaunch-refused | journal | runner/runner-control.ts | The convergence loop's own relaunch (door: converge-relaunch) reached a run whose failure streak is spent — halted failure-streak, or consecutiveFailures at max — and was refused (RCV-3): only a person's press relaunches such a run, and the counter is carried, never zeroed, by anything automatic. The actor rides along. | | run.relay-armed | journal | runner/runner-base.ts | The relay armed for this run (phase 14): a relay: last-resort run at a CLI whose system/init.claude_code_version (version) is at or above the floor (2.1.268). Its phase sessions from now carry the presence-only permission host and its settings the PermissionRequest hook. Once per change. | | run.relay-degraded | journal | runner/runner-base.ts | An armed session's own system/init showed the relay cannot work in it: reason: tool-absent (AskUserQuestion missing from system/init.tools) or host-not-connected (the host's status in system/init.mcp_servers — never read from the exit code, which --strict-mcp-config does not change). Once per run per reason. | | run.relay-refused | journal | runner/runner-base.ts | A relay: last-resort run the relay would not arm for (QRL-5, AC-13): reason: version-unknown (no session on this console has reported a system/init.claude_code_version for the installed binary yet — the next spawn after one does arms) or below-floor (version under floor, 2.1.268, where PermissionRequest does not fire in --print). The run keeps the floor — --permission-prompts none, no host, no PermissionRequest hook — and a question is answered by policy. Once per change. | | run.reconfigured | journal | runner/runner-control.ts, service-runs.ts | The run's settings were patched mid-flight — patch (the fields as applied), before (each one's value before it, null for unset) and the derived actor (by, via, origin, remoteUser, reason) — since control-tower phase 13 (#31); before it the patch's fields sat at the top level with a bare by. A patch that landed on a STORED run (no loop behind it) is journalled too, with stored: true (phase 77, #101): a run whose resumeOnRestart, relay or accounts changed with no line saying so could not explain its own next restart. A qa in the patch is the plan's QA gate moving, before.qa the regime it left. | | run.records-repriced | log | service-base.ts | At boot, this many stored runs were brought to the booked-delta cost model (costModel: 2, control-tower phase 46, #62) — each one's own line is run.cost-repriced. Once per run, ever. | | run.records-remeasured | log | service-base.ts | At boot, this many stored runs were brought to the every-session clock model (clockModel: 2, control-tower phase 58, #66) — each one whose figures moved has its own run.clocks-remeasured. Once per run, ever. | | run.records-swept | log | service-base.ts | Old run records nothing will read again were dropped. | | run.reprice-failed | log | service-base.ts | A stored run's boot re-price could not run (its journal or file unreadable): the run keeps its old figures and no costModel, and the next boot tries again. | | run.remeasure-failed | log | service-base.ts | A stored run's boot re-measure could not run (its journal or file unreadable): the run keeps its old windows and no clockModel, and the next boot tries again. | | run.recheck | journal | runner/runner-control.ts, service-runs.ts | The verdict of a recheck recovery (RCV-4): confirmed (the three checks now agree the phase is done), unchanged (the same ending as before — the halt keeps its clock, no second phase.halted, no streak charge; the run reads exactly as it did) or changed (a different ending, or the checks passed without the phase reading done). Carries the fingerprint it ran under and the halt it left. Since control-tower phase 111 (#204), a recheck of a phase parked on a declared EXTERNAL wall asks the wall's own refs through the watch clock instead (wall: true): landed (one landed — ref, read, resumed — and the scheduler's landing resumes the phase's own session) or waiting (refs: each ref and what it read, or why when none can be asked; the wall, its halt, its errand and the record are left exactly as they were). | | run.recover | journal | runner/runner-control.ts | A recovery was started — its mode, its phase, and who asked. Since 5.0.0 also the evidence fingerprint it ran under (null when the caller had no board) and recovers against max (RECOVER_MAX_PER_PHASE), the verb's own per-phase ledger (RCV-4). | | run.recover.refused | journal | runner/runner-control.ts, service-runs.ts | A recovery was refused: why is the orphan condition, or since 5.0.0 (RCV-4) unchanged — the phase's evidence fingerprint is the one the last recovery ran under (since names it), so running again could only re-write the same halt — or capped, past max recoveries of one phase (recovers); an operator's Retry clears the ledger. | | run.recovered | journal | runner/runner-control.ts | The recovery finished and the run may be driven again. Since control-tower phase 14 (#40), a recovery that ends on a wait with NO clock (its declaration met a spent wait budget) carries parked: wait-budget and leaves the run parked with a budget sentence, never a waiting run that "resumes at undefined". | | run.recovery-continue | both | analysis/timeline.ts, service-base.ts, service-recovery.ts | A recovery's result let the run continue, and it was restarted. Since control-tower phase 81 (#105) the session vehicle also journals it on the run, with the phase the rung fixed, its situation and rung: it fires once the recovery has SETTLED (it used to read the run at the moment the recovery was armed, and so never saw the fix), with the board re-read showing the phase done. | | run.recovery-continue-failed | log | service-base.ts | That restart threw; the run stays where the recovery left it. | | run.recovery-continue-frozen | log | service-base.ts, service-recovery.ts | The restart was due and the console is frozen. | | run.recovery-parked | both | analysis/timeline.ts, service-base.ts, service-recovery.ts | A rung fixed its phase and the run was NOT continued, because a decision is needed (control-tower phase 81, #105): why says which — the run is halt-on-everything (the operator continues it), an errand still stands, or the board re-read does not show the phase done — with the phase, and the situation and rung on the session vehicle's line (by: pty-agent on the agent's). | | run.released | journal | runner/runner-control.ts, service-runs.ts | A held queue was released and admission resumes. Since (control-tower phase 96, #142) by and reason when the Release carried them, and written for a stored run too (stored: true and the actor). | | run.resolved | both | service-runs.ts | The run was marked resolved — nothing more is owed on it. Since (control-tower phase 96, #142) also a JOURNAL line on the run a person dismissed: note when the dismissal carried one, stored: true and the actor — the record keeps only the latest dismissal, so its note was the one decision no history could show. The log line is the board resolver's own auto-resolve. | | run.resume-settings | journal | runner/runner-control.ts | A resume (start {resumeRunId}) changed settings the stored run already had (control-tower phase 77, #101 #102): overridden names each one {from, to} (null = not set), with the actor's fields. The stored run is the source of truth, so a setting the start did not name is kept and never journalled; no line means the resume changed nothing. | | run.decisions-reread | journal | runner/runner-control.ts | A resume re-read the plan's decision manifest (#226, one-pool phase 22): the plan's written ## Decisions rows, its twin merged over them, replaced the stored run's rows for their keys (rereadManifest in server/prelude.ts; every other row, one the plan stopped writing included, stands as the launch resolved it), and changes lists each row that moved — {key, change: added|changed, from?, to}, values cut to 200 characters; empty when nothing did. applied says whether the run took them: only a PERSON's resume does (isPersonsAct), because the rows are in the tree a session can write — a door no person pressed (converge, a watch, a boot sweep) applies nothing and the next press does. held lists rows the plan now leaves unanswered, which no resume applies. Written on every resume that carried a stored manifest, with the actor. Before it, a manifest was resolved at first launch only, so a row amended since never reached the live run. | | run.rung-still-driving | log | service-recovery.ts | A rung was asked about and its vehicle is still running. | | run.secrets-pruned | log | runner/runner-control.ts | Per-run secret files for a finished run were removed. | | run.secrets-swept | log | service-base.ts | Secret files no live run claims were removed. | | run.settings | journal | runner/runner-control.ts | The run's --settings file was written, with the profile it encodes. | | run.settings.prune-failed | log | runner/approvals.ts | Pruning a settings file threw. | | run.settle-pending | journal | runner/runner-loop.ts | The run's branch is queued to merge and the settlement is not finished. | | run.settle-unsupported | journal | runner/runner-loop.ts | The settle strategy cannot apply to this run's checkout shape, and why. | | run.settled | journal | runner/runner-loop.ts | The run's branch and checkout were settled — or deliberately left as they are (strategy, branch). An integration settle carries the merge's own fields (into, kind, …) and, on a MIRROR, one line per mounted repository with its repo key, deepest first (5.1). A pr settle that opened no run-branch pull request because every phase landed by its own says so in note. | | run.trace | journal | runner/runner-loop.ts | One snapshot per drive naming this run's correlation id (5.1.0): traceId = runTraceId(instance, slug, runId), with instance and the console's pid. DERIVED, never minted — a resumed run recomputes the same id from three facts it re-reads off disk, so the second half of a run joins the first. It is on every journal line of the run as the traceId column too; this row exists so a reader with only the journal can find the id without knowing how it is derived. | | run.tree-hold | journal | runner/runner-loop.ts | A phase's session left a SHARED repository on this run's branch, so the RUN now holds it until it settles (control-tower phase 40, #41): repo, branch, head, foundOn (what it stood on before the run) and phase. Once per repository per run, beside a record under <state>/trees/ that the scheduler's branch holder and phase-lock.sh conflicts both read. | | run.tree-released | journal | runner/runner-loop.ts | The run settled — finished, or stopped by an operator — and gave up a shared repository it held: repo, branch, and either to (the branch it was found on, which it was switched back to) or kept (why it was left where it stands: uncommitted changes, moved since, another run holds it, found detached). | | run.drive | both | runner/runner-loop.ts | Not an event: the NAME of the span one drive runs inside (5.1.0). Every console-log line written under it carries the run's traceId, which is what makes grep <traceId> answer "what happened to run X" instead of the six-step walk. | | phase.attempt | both | runner/runner-attempt.ts | Not an event: the NAME of the span one phase attempt runs inside (5.1.0), carrying phase and attempt. Nested in run.drive; the session spawned under it inherits the four env carrier keys, so the session's own task list, outcome, lock and presence record join the same trace. | | phase.wall-reread | journal | runner/runner.ts | A usage wall's park SHORTENED to an earlier reset the account now reports (#78): from → to, by (reading, spend, reprobe), the account and bucket. A later reset never lengthens a park. | | phase.wall-lifted | journal | runner/runner.ts | A phase parked on a usage wall came back before the reset it reported (control-tower phase 54, #78): the quota door said the account the phase boards under has headroom again. by is what asked — reading (a fresh meter reading, the service's re-read on every accounts change), spend (a session that spent under the account and ended well) or reprobe (the phase's own back-off re-probe, WALL_REPROBE_BACKOFF_MS) — with the account, the wall's bucket, the clock it was parked until, and the probes so far; walled names the account the wall was on when a switch has moved the run since. | | phase.model-mismatch | journal | runner/runner.ts | Under **Model policy:** pinned (#91) a session's init reported another model than the one the phase is pinned to — {requested, resolved, sessionId}. The session is ended before it spends and the phase parks with one errand (record.modelMismatch); the [1m] window suffix and a date suffix are not a mismatch (sameModel). | | phase.model-held | journal | runner/runner-attempt.ts, runner/runner-loop.ts | Under **Model policy:** pinned (control-tower phase 54, #91) the run kept the phase's model where the ladder would have moved it: why: 'wall' — a per-model wall that switches account under onLimit, waits for its reset or parks, and never calls nextModel — or why: 'rung', an escalate: model boarding that keeps the model and raises the effort one step (effortFrom → effortTo, beside the rung). | | phase.delegated | journal | service-runs.ts | A person handed the acts a phase kept for a person to that phase's own session (the person slot's Delegate press): by, the rulingId of the deviation ruling it wrote under human-acts, and the session/brief the resume launched. | | phase.errand-answered | journal | service-runs.ts, runner/runner-control.ts | A person answered the errand a phase was parked on — "Done — continue" (POST /api/run/:slug/phase/:n/errand-answered, control-tower phase 88, #124): by, their note, and the errand's situation. The answered declaration is spent and record.errandAnswered stamped (evidence the fingerprint reads), then the phase re-boards with the note as a Resume would. | | run.shutdown-child | journal | runner/runner.ts | One child was ended during shutdown (SHD-8): pid, phase, sessionId, how — interrupted (its turn closed on SIGINT), exited (SIGTERM was needed), killed (neither was, after graceMs), or gone — with the ladder's graceMs and interruptGraceMs, how long it took (ms), why (console-shutdown or restart), the drain's intent and reason, and openTool — the tool call open when the signal went, {name, since, summary}, because a gh pr merge cut mid-call is where which side of the merge is the only question. | | run.situation-failed | log | service-runs.ts | The classifier threw for one phase. | | run.stall-escalated | log | service-live.ts | An unresolved stall was said once more, urgently — only while its phase still runs, in the episode it was armed for (control-tower phase 47, #80). | | run.stall-retracted | log | service-live.ts | A stall escalation was withdrawn. | | run.start | journal | analysis/timeline.ts, runner/runner-control.ts, service-runs.ts | The run started, and its timeline axis begins here. Since 5.0.0 it carries the ACTOR whole (shared/run-lifecycle.js ACTOR_FIELDS): by, via (the transport), origin, remoteUser (the proxy's login or null) and, for a start nobody pressed, door — one of the fourteen START_DOORS — with trigger (what fired it), guard (the predicate that let it through) and counter (the bound it spent); a person's press carries door: operator. minted: true marks a record written for a run the console minted around a session it did not start (the outcome inbox, a QA recovery verb) rather than a loop starting. by: unattributed is a harness calling Runner.start bare and never a door's word. | | run.start-refused | journal | runner/runner-loop.ts, service-base.ts | The instance's start ceiling (start-ceiling.ts) refused an automatic start: the actor that asked (with its door), which ceiling (startsPerHour or usdPerHour), the limit, the count inside the sliding hour, and until — when the oldest entry leaves the window. Written on the run the door would have started; the console log gets start-ceiling.refused when there is none. Announced on health once per window. A person's press is never refused. | | run.token-scope | journal | runner/runner-attempt.ts | A run paying as a TOKEN account boarded a phase with MCP servers (ACT-12): the token rides the whole child process tree (reach: 'child-tree'), so only the stdio servers the PLAN declares (declaredByPlan) and the remote ones are kept; the run's and the phase's own stdio picks are dropped. Written once per boarding so the session's missing tools have a line explaining them. |

| run.key-scope | journal | runner/runner-attempt.ts | A run paying as an API-KEY account (one-pool phase 3) boarded a phase with MCP servers: the key rides the whole child process tree (reach: 'child-tree') and is money, so only the stdio servers the PLAN declares are kept — the run's and the phase's own picks, and any remote server the plan did not name, are dropped. servers, kept, dropped, declaredByPlan. Written once per boarding. |

| run.token-kept | journal | runner/runner-control.ts | A run's loop ended while a child launched under it still lived, so the hook token baked into that child's settings stayed ARMED rather than being retired under it (#74): the loop adopts the child, and the token is retired once the last holder has gone. pids. Written once per ending that kept it. | | run.hook-unauthorised | journal | service-base.ts | A session one of this console's runs launched had a hook call refused 401 (#74). It is still working — with no permission policy, no ask card and no wait guard, because the CLI does not block a tool on a failed hook — so this is a run-level fault, written once per session, and the inbox carries it as a health row with the count while the session lives. sessionId, hook, reason, armed. | | run.model-resolved | journal | runner/runner.ts | What a model request resolved to, as the session's own init said (control-tower phase 54, #91): {requested, from, to}, once when a request first resolves in a run and again each time it resolves to a DIFFERENT model — an alias that moved. Kept on the run as resolvedModels. | | run.note | journal | analysis/timeline.ts, service-runs.ts | A person's note on the run (control-tower phase 96, #142): id, text, pinned and the actor — by, via, origin, remoteUser — filed on the phase it names, else run-level. Kept on the run record as notes (every pinned note and the newest 200 others); the timeline draws it as a note tick; a PINNED note is read into the boot prompt of every phase it applies to until it is unpinned. | | run.note-pinned | journal | service-runs.ts | A note was pinned or unpinned (control-tower phase 96, #142): id, pinned and the actor. A standing decision stops being one only when a person says so. | | run.stop-requested | journal | analysis/timeline.ts, runner/runner-control.ts, service-runs.ts | A stop was requested for the run, naming the children it will end — and, since 5.0.0, WHO (LFC-6, SHD-3): the derived actor (by, via, origin, remoteUser) that also decides stoppedBy (stoppedByOf: a request, a signal or a hook body is the operator's; the console's own clocks, boot and observations are the system's). stored: true marks a stop that landed on a checkpoint no loop was driving, and foreign: true beside it (5.2.0, #21 §4) a child of an EARLIER console that this one ended through signals.ts' ladder — ending is how it went. That used to be a 409 telling the operator to kill the pid themselves; a pid whose recorded (pid, procStartedAt) the kernel disagrees with is still refused, because it has been recycled. | | run.stopped-while-idle | journal | runner/runner-control.ts | The run was stopped while it had nothing in flight — was is the status it read, and the actor rides beside it as on run.stop-requested. | | run.thawed | journal | analysis/timeline.ts, runner/runner-control.ts | The freeze was lifted and the lanes are running again. | | run.ultrareview | journal | runner/ultrareview.ts | A cloud claude ultrareview was started over this run's work, and which checkout it reads. | | run.ultrareview-done | journal | runner/ultrareview.ts | The cloud review ended: its verdict and finding count, or ok: false and the reason it could not answer. | | run.ultrareview-skipped | journal | runner/runner-loop.ts, runner/ultrareview.ts | No cloud review ran — a freeze, no review surface, or a run that finished no phase to hang one on. | | run.usage-decision | journal | runner/runner.ts | A usage warning past the alert threshold, decided once per window and reset: action (throttle, switch, park) from the run's onLimit, thresholdPct, utilizationPct, window, resetsAt, policy. The switch and the park are carried out at WALL_PCT by the live wall (phase.live-wall) — phase 8 ruled the scheduler's throttle IS a learned wall, so enacting either at 95 % would refuse the account a start with 5 % left. Since autopilot-token-drain phase 6 the decision engages the usage BRAKE instead (run.usage-brake) unless a switch has somewhere to go, and says what was done: enacted (true only for a throttle, which the brake carries out), brake (the account is braked after this line), headroom (a switch only: the account pickAccount answered, or null), and nonRunSessions — the live sessions no run spawned, {byAccount, unattributed} by the config dir their presence hook reported (the per-account numbers are floors while unattributed > 0), or unknown when the console has no registry to ask. Since control-tower phase 92 (#100 ask 3) a switch with somewhere to go is ENACTED at the lanes' boundary (run.account-switch {when: 'boundary'}): headroom is the forecast picker's answer (Accounts.switchCandidates against this window's reset and the run's floors), enacted is true once the run moved, when: 'boundary', and why says what stood in the way whenever a switch was not enacted. | | run.usage-carried | journal | runner/runner.ts | A usage warning past the alert threshold on an account whose credits carry it past its plan windows (control-tower phase 93, #146: the operator's overage: allowed and the account's own credit state both say so — Accounts.creditCarries), said once per window and reset instead of a run.usage-decision: accountId (the account the lane spends), utilizationPct, thresholdPct, window, resetsAt, usingOverage when the session said. Nothing is switched or braked, and the warning is NOT marked decided — the first warning after the credits stop is decided as ever. The live wall likewise leaves a carried session alone at WALL_PCT and on a rejected window the CLI says it is paying past on overage (isUsingOverage); a refusal of the credit itself (overageStatus: rejected, an overageDisabledReason) is a wall again at once. | | run.usage-brake | journal | runner/runner.ts | An account's usage brake was engaged (autopilot-token-drain H6): a reading past ALERT_PCT with nowhere else to spend, so the scheduler admits no NEW lane on the account while one is live on it (Scheduler.brake — never the only lane). accountId, utilizationPct, thresholdPct, window, until (the window's reset, or null), live (lanes on the account now). Written once per engagement, by the reading that engaged it. | | run.usage-brake-released | journal | runner/runner.ts | The brake came off: reason below-warn (a reading of the brake's own window under WARN_PCT, with its utilizationPct) or reset (the first reading after the window's reset — the scheduler already stopped honouring it at the reset), and heldMs. A reading of another window releases nothing. | | run.usage-window | journal | runner/runner.ts | The account's usage window as the CLI reported it, whenever it is not plain allowed: utilization as the wire's fraction and utilizationPct as the meters' percent. | | run.verify-approvals | journal | runner/runner-control.ts | The start door's answers for §Verification (2026-09-18): how many exact commands the operator approved that the built-in tier would not run (approve) and how many fragments it set aside (waive), and who (by). The texts ride on the run file (RunState.verifyApprovals); an approval is bound to a command's whole text and relaxes only "is this a command I know". | | run.wait-overdue | both | service-base.ts | A wait clock went by while nothing ran, and the console RULED on it before resuming anything: until, now, lateByMs, what each declared ref says now (refs), and the presence of each session that declared a park (declarers). | | run.waiting | journal | runner/runner-control.ts | The run is waiting on a clock, until when and why. | | run.waiting-person | journal | runner/runner-control.ts | The run is waiting on a PERSON (WAI-10): a verification card or a tool approval card stands for phase, cardId names it, until is the card's expiry (the run's clock) and on says which card; cards counts those standing. The run reads waiting with waitReason: person until the last card is down — it used to read running with no child for up to twelve hours. | | run.waiting-external | journal | analysis/timeline.ts, runner/runner-control.ts, runner/runner.ts, service-runs.ts | Every phase that could start is parked on somebody else's clock — composed once, whichever path parked the last one. Since control-tower phase 50 (#53) it can carry beside — {phase, kind, reason}, the stop a sibling phase ended on: that stop would have PARKED the run and dropped this clock, so the run waits on the clock instead and the stop stands beside it. | | run.walls-lifted | journal | runner/runner-base.ts | A session that spent under the account and ended by itself proved the account can pay (#78): the facade's noteSpend lifted its shared walls and the spent model's own (lifted) and ended a usage cooling (cooled). Written only when something moved; every parked wall of the run is re-read right after (phase.wall-lifted {by: 'spend'}). | | run.watch-landed-frozen | log | service-recovery.ts | A watch landed and the fleet is frozen, so the resume did not happen. | | run.watch-resume-failed | log | service-recovery.ts | A watch-landed resume threw. Said ONCE per distinct error per lease (until, a foreign lock's lease_until) — rejections counts the landing's refused drives, the re-offer backs off along WATCH_REDELIVER_SERIES_MS (or to the lease's end), and at MAX_BOOT_RESUMES the landing becomes an errand (SLF-8, RCV-8). It used to be said every minute for the whole lease. | | run.watch-resume-settle-failed | log | service-recovery.ts | Settling a watch-landed resume threw. | | run.watch-resume-void | log | service-recovery.ts | A watch-landed delivery launched nothing, so the delivery was UN-charged. busy: true (control-tower phase 6) when the recovery door refused because the run was live (RunBusyError): that is never counted as a rejection, and it writes no errand. | | run.window-lifted | journal | runner/runner.ts | A run asleep on a usage window inside an attempt (waitOutWindow) was woken early because its account has headroom again (#78) — account, by (reading, spend, reprobe) and the clock it was waiting until. | | run.worktree-discard-failed | log | runner/runner-loop.ts | Discarding a worktree threw. | | run.worktree-release-failed | log | runner/runner-loop.ts | Releasing a worktree threw. Never fails the run. | | run.worktree-include-refused | journal | runner/runner-loop.ts | A .worktreeinclude named paths the checkout would not copy — outside the repository, missing, or past the 200-file / 50 MB cap. Named rather than dropped: to the build that needed the file, a silent refusal reads exactly like a bug in the checkout. | | run.worktree-locked-foreign | journal | runner/runner-loop.ts | A sweep left a worktree alone because somebody else locked it — an operator's own git worktree lock, another tool's. dir and the reason they gave. Said once per tree: the answer does not change until they unlock it. | | run.worktree-released | journal | runner/runner-loop.ts | The run's worktree was released — what was removed and what was kept. | | run.worktree-setup | journal | runner/runner-loop.ts | The worktree's setup command ran, with its output. | | run.worktree-setup-discarded | journal | runner/runner-loop.ts | A worktree whose setup failed was discarded. | | run.worktree-sweep-failed | log | runner/runner-loop.ts, service-base.ts | The worktree sweep threw. | | run.worktree-unavailable | journal | runner/runner-loop.ts | A worktree could not be had at all, with the refusal. | | run.worktrees-kept | journal | runner/runner-loop.ts | The loop ended RESUMABLE — paused, halted, waiting, parked, interrupted with resumeOnRestart (runResumable) — so its trees were left standing (control-tower phase 82, #94): status, reason: resumable, dir when the run holds a checkout. Retention is a question about a run that is over; the prune happens when this run finishes or is resolved. | | run.worktrees-pruned | both | runner/runner-loop.ts, service-base.ts | Stale worktrees were pruned — including registrations whose directory is gone (unmanaged). Since control-tower phase 112 (#184) untracked files never pin a mirror mount: they move to <run>/stale-mounts/<stamp>/<mount> (preserved, kept like any quarantined content) and the mount goes; a mount kept for tracked edits is named in a run.errand beside this line. | | run.worktrees-unmanaged | both | runner/runner-loop.ts, service-base.ts | A working tree this console did not make is holding a pe/* branch. Reported, never removed. | | run.worktrees-swept | both | runner/runner-loop.ts, service-base.ts | The worktree sweep ran across the console's runs. trigger (one-pool phase 20, #333): boot, or resolved — a run of the plan was just marked resolved (a dismissal, converge closing a plan, a recovery, the board's resolver), so its trees are swept then rather than at the next restart. spared (control-tower phase 82, #94): the run ids passed over whole because the stored run is resumable (runResumable) — the boot sweep runs before any runner re-registers, so "not live" was read as "over" and a paused run's clean mirror mounts were taken at every restart. |

Console log events

The log sink's own names — lines about the CONSOLE rather than about the work: what it did at boot and shutdown, which door it refused, which clock fired, which file it could not read. They carry no fixed prefix (the first segment is the subsystem — shutdown, mcp, push, terminal, runner, …), so viewer/test/docs-parity.test.ts reads them off the CALL — the first argument of log.info/warn/error(…), and of the sessions registry's onWarn/onInfo callbacks that service-base.ts forwards to log.warn/log.info — rather than off a prefix. Same columns, same rule as the table above: every name written under viewer/server/ has a row here, and every row names something written. The run.* and policy.* log names sit in the table above beside the journal's, because their prefix puts them there. The audit that widened the check (sep-review, LFC-4) found 70 of the 90 names the two consoles had actually written in their logs outside the checked alphabet; the source writes these 247.

eventsinkemitted bymeaning
access.refusedlogindex.tsThe remote-access gate turned a caller away — reason is which rule refused (unknown-host, no-identity, not-allowed, or a proxied request asking for a loopback hostname), with the host, the proxy's login and the path. Only ever written with --remote set; upgrade: true marks a WebSocket handshake, whose query is stripped because it carries the terminal token.
access.remotelogindex.tsA remote identity was SERVED for the first time in this process (FLT-10): {host}, {loginHash} — the first 12 hex characters of the login's sha256, never the login itself — and {first}, {last}, {count} as they stood. Exactly one line per identity per process; the running counters are on state().access (served.local, served.remote, lastRemoteAt), and the first remote request also writes lastRemoteAt onto the instance's registry row.
accounts.emit-failedlogservice.tsListing the accounts for the debounced push to open pages threw, so every browser keeps the account list it last saw. Display only — nothing a run does depends on it.
accounts.entitlement-probe.ranlogaccounts/index.tsA person's one-turn check of whether an account may run work finished (zero-touch-console phase 15, ACT-8): status is the probes' word — ok the API took work under the credential (a completed turn, or a usage limit or spent cap, which are answers too), fail it refused the credential (class: org-policy, auth, billing, certificate — and the account is RETIRED with its organisation, as a phase's refusal would), skip it could not be asked (spent: false for a signed-out login, an unsigned profile or a missing token, refused before any spend; a timeout or an overloaded API otherwise). With the request's actor, the session's ending, ms, costUsd, turns and cliVersion, and from/to when the breaker moved. The newest answer and a count are kept on the credential's probe record in the machine-wide learned store.
accounts.entitlement-probe.sigkilllogaccounts/entitlement-probe.tsThe one-turn check's session ignored SIGTERM through its grace and its process group was SIGKILLed. The CLI leaves on the TERM and runs its SessionEnd hook, which is what the grace is for.
accounts.learned.adoptedlogaccounts/index.tsA 4.1.0 registry's per-row walls (limitedUntil, the reserved default row included) were folded into the machine-wide learned store (<state home>/accounts/learned.json) and dropped from the registry — once, on the first construction over the old file; accounts names the rows.
accounts.learned.transition-refusedlogaccounts/learned.tsA write asked the breaker for a move ENTITLEMENT_TRANSITIONS does not allow (from → to, by) and was refused with the state standing — a retired credential does not become entitled because a poll succeeded; only a person's clearance or a new orgId opens it, to unknown.
accounts.learned.unreadablelogaccounts/learned.tsThe machine-wide learned store did not parse, so this read degraded to EMPTY — no walls, no breaker, no tombstones — and every console on the machine reads the same file. The next write re-reads under the lock; fix or remove the file rather than letting a corrupt one stand.
accounts.login.complete-failedlogservice-base.tsA sign-in terminal exited and reading the profile's identity back afterwards threw, so nothing was announced and the card still reads as not signed in. The credentials may well be there — open Accounts and re-check before signing in again.
accounts.login.probe-failedlogaccounts/index.tsclaude auth status under the profile's own config dir threw while reading back who just signed in, so the email and plan fall to the credentials file alone. A degraded row, not a failed login: the account can be signed in and still show no address or plan.
accounts.profile.createdlogaccounts/index.tsA profile account was registered and its private config dir made with 0700. The sign-in itself has not happened yet — that is the terminal the console puts in front of the operator next.
accounts.registry.unreadablelogaccounts/store.tsThe registry file did not parse or carried a version this build will not read (a missing file is silent), so the console degraded to an EMPTY account list. Every registered account is invisible and runs fall back to the machine login — fix or restore the file before adding accounts on top of it.
accounts.removedlogaccounts/index.tsAn account was forgotten: its row, its stored token or profile credential, and its config dir are gone. kind says which sort it was; the machine login is never the subject, because it cannot be removed here.
accounts.renamedlogaccounts/index.tsAn account's display name was set or cleared — named: false means it went back to its default label. Ids never move, so nothing that keys on one (journals, throttles, paths, the keychain hash) changes.
accounts.retired.clear-requestedlogservice.tsAn operator asked to clear a retired account (POST /api/accounts/:id/clear-retired), with the request's derived actor; cleared: false means the account was not retired.
accounts.retired.clearedlogaccounts/index.tsThe breaker opened for a retired credential AND its organisation — every account sharing the orgId — to unknown, by a person (by). The next successful meter read or spend proves it again.
accounts.retired.reopenedlogaccounts/learned.tsAn authenticated read LANDED later than a certificate-class retirement, so the breaker opened by itself: fingerprint, by: proof, class, at. The one automatic door out of retired, and it opens for exactly one class — a successful call is direct evidence about the network path and no evidence at all about an organisation's policy. Measured: the poller succeeded hourly for two and a half hours against seven credentials the breaker called unusable, and every one of those answers was discarded.
accounts.certificate.corroboratedlogaccounts/index.tsEnough certificate-shaped stops to believe one: account, strikes, sessions, spanMs. Three strikes, from at least two sessions, spanning at least ten minutes, with no successful read between (CERT_STRIKES_NEEDED, CERT_SESSIONS_NEEDED, CERT_SPAN_NEEDED_MS). The credential then goes cooling for ACCOUNT_COOLDOWN_MS — never retired, and never the organisation's row.
accounts.suspect.clearedlogaccounts/learned.tsA suspect retirement cleared (#57): a green check, a spend, or a second green read at least SUSPECT_CLEAR_MS after the demotion — by and at.
accounts.spend-provedlogaccounts/index.tsA session spent under the account and ended well (#78, #57): what that proved — the walls lifted, a usage cooling ended (cooled), and the breaker's move out of suspect or a classifier's retirement when there was one.
accounts.retired.suspectlogaccounts/learned.tsA classifier's retirement was demoted to suspect (control-tower phase 54, #57): a green usage read (or check) landed after it. The credential — or its organisation — stays out of the rotation until a check, a spend, or the contradiction standing SUSPECT_CLEAR_MS clears it. A person's retirement and a refused check are never argued with.
accounts.token-scopelogrunner/runner-attempt.tsA token account's run DROPPED stdio MCP servers the plan did not declare (ACT-12) — dropped names them; the journal's run.token-scope on the run carries the whole set.

| accounts.key-scope | log | runner/runner-attempt.ts | An API-key account's run DROPPED MCP servers the plan did not declare as stdio (one-pool phase 3) — dropped names them; the journal's run.key-scope carries the whole set. | | accounts.api-key.added | log | accounts/index.ts | An API key was registered (one-pool phase 3, POST /api/accounts/key or phase-console accounts add-key): account, provider. The key was checked by shape for its provider and stored in the keychain item phase-console-account-<id> (else the 0600 file accounts/<id>/token); the registry keeps only its last four characters. Nothing was spent — Test proves it. | | accounts.api-key.replaced | log | accounts/index.ts | An API-key account's key was replaced under its own id (PUT /api/accounts/<id>/key or accounts replace-key): account, provider. Its last test spoke for the old key and was dropped; the learned row for the old credential was forgotten. | | accounts.api-key.tested | log | accounts/index.ts | One models call with an API-key account's key (POST /api/accounts/<id>/test or accounts test): account, provider, standing (ok, refused, unreachable, unexpected — KEY_STANDINGS), the HTTP status and how many models the list named. It spends nothing; the answer is the account's lastTested. | | accounts.api-key.standing | log | accounts/index.ts | The bridge learned a key's standing from its provider on a session's request (one-pool phase 8): account, the upstream's HTTP status, standing — refused (401, 403) or unfunded (402, or a 429 whose code is insufficient_quota) — and the provider's reason, its key redacted. Written as the account's lastTested, so the pool stops placing sessions on it until a Test or a new key says otherwise. | | bridge.started | log | pro/providers/bridge/server.ts | The console's OpenAI bridge began listening on 127.0.0.1 (one-pool phase 8, §Architecture 5) — when the first bridged session's environment was composed, or at boot under live tokens: port, the preferred port it asked for (the last one it held, bridge.json) and whether it was reused. | | bridge.stopped | log | pro/providers/bridge/server.ts | The bridge stopped listening: port and reason — idle (ten minutes with nothing in flight and no live token), console-stop, or a smoke's own bridge closing (smoke-done, smoke-refused). | | bridge.request-failed | log | pro/providers/bridge/server.ts | A session's request through the bridge did not come back whole: account, the upstream's status (null when it could not be reached), its error code, and the reason in the provider's words with the key redacted; stream: true when a streamed answer broke or failed mid-way. The session was answered in Anthropic's error shape, and the request's ledger row reads error or rate-limited. | | bridge.request-refused | log | pro/providers/bridge/server.ts | A request reached the bridge with a token that does not open its account's path — why: missing, unknown, expired or other-account — and was answered 401. The token itself is never written. | | bridge.tokens-save-failed | log | pro/providers/bridge/tokens.ts | The bridge could not write bridge-tokens.json (the tokens' SHA-256 hashes, never the values): the tokens still work in this process, and a console restarted before the next write will answer their sessions 401. | | bridge.state-save-failed | log | pro/providers/bridge/server.ts | The bridge could not record the port it listens on (bridge.json): a restarted console picks a new one, and a session still pointed at the old one fails its next request. | | bridge.ledger-failed | log | pro/providers/bridge/server.ts | A request's ledger row could not be written (account, error): the request was served, and its spend is missing from the account's statistics. | | bridge.resume-failed | log | accounts/index.ts | At boot, live bridge tokens were found but the bridge could not listen again (error): the sessions holding them fail their next request until a new session starts it. | | accounts.settings-set | log | accounts/index.ts | An account's settings were changed (PATCH /api/accounts/<id>/settings or accounts set): account and the top-level fields the patch named. Kept on the account's row (the machine login's as the registry's defaultSettings). | | accounts.paused | log | accounts/index.ts | An account was paused (POST /api/accounts/<id>/pause or accounts pause): its settings read enabled: false, so no rank picks it and the quota door refuses a run on it by name until it is resumed. | | accounts.resumed | log | accounts/index.ts | A paused account was put back (POST /api/accounts/<id>/resume or accounts resume): enabled: true. |

| accounts.registry.upgraded | log | accounts/store.ts | The account registry was written by an older build (from) and was upgraded in place to this build's version (to, 2 since one-pool phase 3) on its first read; the old file was copied aside first (backup, accounts.json.v<N>.bak, once). |

| accounts.ledger-append-failed | log | pro/accounts/ledger.ts | A usage row could not be appended to the ledger file (accounts/usage.ndjson; one-pool phase 6) — session, seq and the error. The row is not written and the session goes on; nothing else is retried. | | accounts.ledger-compacted | log | pro/accounts/ledger.ts | The ledger folded its rows older than 90 days into accounts/usage-daily.json (per day, account, kind, provider, model, plan and session kind) and rewrote the raw file without them — folded, kept. | | accounts.ledger-compact-failed | log | pro/accounts/ledger.ts | The ledger's compaction failed (the error); the raw file is left as it was and the next hour tries again. | | accounts.ledger-unreadable | log | pro/accounts/ledger.ts | The ledger file could not be read (the error); the reader answers from the rows it already holds. | | accounts.meter-end-failed | log | pro/accounts/meter.ts | A session meter could not write its last row when the session ended (the error); the session's outcome is unaffected. | | accounts.meter-event-failed | log | pro/accounts/meter.ts | A session meter could not fold one stream event (the error); the event still reaches its session's own reader. | | accounts.meter-probe-failed | log | accounts/index.ts | An entitlement probe's one turn could not be written as its ledger row (account, the error); the probe's verdict stands. | | accounts.meter-tick-failed | log | pro/accounts/meter.ts | A session meter's five-minute write failed (the error); the next result or tick writes what it owed. | | account.smoke | log | pro/providers/smoke.ts | A provider smoke ran (phase-console accounts smoke, scripts/provider-smoke.sh, POST /api/accounts/<id>/smoke; one-pool phase 5): ONE real tool-using session on an API key's recipe — provider, account (null for a key read from a file), the model asked, sent and served (model), ok, costUsd (the catalog's price at the session's instant) and durationMs. Never the key, never a path. | | account.spilled | journal | runner/runner-control.ts | The pool spilled (one-pool phase 7, §Architecture 6): none of the kind its policy prefers had room, and the other kind took the session — from (null at a start), to, at (start, boundary or wall — SPILL_MOMENTS), the policy that picked, and words, the verdict's sentence (spillWords: "no subscription in the pool has room — spilled to "). | | account.capped | journal | runner/runner.ts | A spend cap of the lane's own API-key account was reached by the turn that just closed (one-pool phase 7): account, window (day, week, month — CAP_WINDOWS), resetsAt, spentUsd, capUsd, sessionId. The session is checkpointed; a pinned pool parks the phase (needs-human --needs accounts), onLimit: switch spills to another account of the pool, and otherwise the run halts budget — "Cap reached on : ", with halt.cap naming the three recoveries (raise the cap, switch account, hold). The halt's cap field is {account, window, resetsAt, recoveries, label, words, line, body}: account the id, window a CAP_WINDOWS word, resetsAt an ISO instant or null, recoveries the ids raise-cap, switch-account and hold (CAP_RECOVERIES, shared/pool-model.js) the halt card draws as Raise the cap on , Switch account and Hold the run, and label, words, line and body the account's name and the composed sentences. | | catalog.refreshed | log | pro/providers/catalog.ts | A provider's models list was read with a registered API key (the six-hour refresh, a picker opening — GET /api/providers/models?refresh=1 — an id nobody knew, or a Test pressed on the account) and replaced its entry in the model catalog: provider, account (whose key read it) and how many models it holds after normalizing (OpenAI's embedding, voice and image models are left out). Cached in <instance state>/providers/catalog.json. One-pool phase 4. | | catalog.failed | log | pro/providers/catalog.ts | A provider's models list could not be read — standing (refused, unreachable, unexpected — KEY_STANDINGS) and the HTTP status — so the catalog KEPT its last list (kept models) and recorded the standing; or, with write, the cache file could not be written. One-pool phase 4. |

| accounts.token.added | log | accounts/index.ts | A pasted setup token was stored for this account, the usage poller was restarted on it, and anything learned about the previous holder of that id was forgotten. The token is validated by shape only; it is proven the first time something runs under it. | | accounts.token.replaced | log | accounts/index.ts | A token account's credential was REPLACED under its own id (PUT /api/accounts/<id>/credential, control-tower phase 13, #33) — the repair re-pasting used to perform by minting a second account. The learned row for the old credential (walls, breaker, tombstone), the poller's verdict and the login verdicts were forgotten, and the token's year counts again from now. | | accounts.token.missing | log | accounts/credentials.ts | A run asked for the environment of a token account and the keychain had no token for it, so no credential was set and the child inherits the machine login instead of the account the run names. Re-paste the token from Accounts. | | accounts.transcript-layout | log | service-base.ts | At boot: does the machine login's config directory hold transcripts the way the switch-and-resume path reads them (projects/<escaped-cwd>/<uuid>.jsonl)? ok, whether projects exists, how many project directories and how many hold a transcript. A warn (ok: false) means the CLI moved its files and every transcript port will report not found until this console learns the new layout (ACT-12). | | accounts.transcript.copy-failed | log | accounts/transcripts.ts | Copying a conversation file into the other account's config dir threw, so a --resume under that credential will not find it. The caller answers by boarding a fresh session with failure context rather than by failing the phase. | | accounts.transcript.not-found | log | accounts/transcripts.ts | The session's conversation file is in neither the source config dir nor the target, so nothing could be carried between accounts and the resume must start fresh. Informational: a session that never wrote one looks exactly like this. | | accounts.transcript.ported | log | accounts/transcripts.ts | A session's conversation file was copied into the config dir named by to, so a resume paid for by that account will find the conversation; cliVersion is the CLI whose layout the copy relied on (null when the console could not ask). Written only when bytes moved — a same-directory switch writes nothing here. | | accounts.walls-reread-failed | log | service.ts | A runner's re-read of its parked usage walls threw on an accounts change (#78). The reading itself stands; the next change or the phase's own re-probe asks again. | | accounts.walls-reread | log | accounts/index.ts | A fresh usage reading re-read the account's learned walls (control-tower phase 54, #78): the buckets lifted (read under WALL_PCT), the ones moved to an earlier reset, and whether a usage cooling ended or was shortened. Only when something moved. | | accounts.overage-set | log | accounts/index.ts | The operator switched an account's "use credits past plan limits" on or off (control-tower phase 93, #146; operator decision 12): account, allowed, capUsd (the operator's own cap, when set — else the API's monthly limit is the cap), by. Kept in the instance's account registry (accounts.json → overage), per account, OFF by default. | | accounts.overage-refused | log | accounts/index.ts | Switching "use credits past plan limits" ON was refused because the account's own credit state says credits cannot carry it (#146): account, reason — out of credits, turned off, the spend limit or cap reached, or credit state unknown when no fresh usage read has reported the account's extra_usage block. The route answers 409 with the same words. | | accounts.credits-changed | log | accounts/index.ts | An account allowed to use credits stopped carrying its runs past its plan windows, or started again (#146) — once per transition, from a usage read or a session's own rate_limit_event (a refusal stops the carry at once, before any poll): account, carrying, reason. The service announces it under limits ("Credits stopped — …"), and a start lifts the account's usage brake. | | accounts.identity-changed | log | accounts/usage.ts | Somebody else is signed in under this account's credential — its fingerprint, organisation or email moved (control-tower phase 76, #109). The meters on hand were the previous identity's, so they are dropped (the bucket names) and the account is read again at once; a read that fails leaves it with no meters rather than somebody else's. | | accounts.identity-rekeyed | log | accounts/index.ts | An account now answers another identity than the one the machine-wide learned store saw it under (control-tower phase 91, #109's deferral, #131): the walls and the breaker word the previous login earned are dropped with its meters, and the service is told — every run bound to the old identity parks. Restart-safe: the key is kept on the learned row. | | accounts.identity-change-failed | log | accounts/index.ts | The service's handler for an identity change threw (error); the re-key itself already happened. | | accounts.keep-alive | log | accounts/index.ts | A login-backed account was renewed through the CLI's own refresh (control-tower phase 91, #147): a claude -p start on a model nothing serves, which the CLI refuses after refreshing ($0, measured on 2.1.283). outcome — renewed, not-due (the CLI judged the token fresh), failed or no-login — and expiresAt when it moved. Runs a few minutes before the access token lapses, and before the poller asks with a lapsed one. | | accounts.login-at-risk | log | accounts/index.ts | A renewal failed on a lapsed access token: this console can no longer renew the login (#147 ask 4). reason. Told once per failure; the service announces it under limits, with the exact fix, when a live or queued run depends on the account. No verdict on the login — expired still comes only from a refused live token. | | accounts.login-restored | log | accounts/index.ts | An expired or signed-out login works again (#147, operator decision 12): a usage read landed after its auth retirement and claude auth status confirmed it, so the retirement is answered (from → to). What lets converge relaunch the stopped run on the same identity with no press; an organisation, billing or policy refusal is never reopened this way. | | accounts.connectivity-lost | log | accounts/usage.ts | A meter read never reached an answer — fetch failed, DNS, TLS, a reset connection (#110). The network, not the credential: the account keeps its own cadence rather than backing off, and the connectivity probe asks at most once a minute until the network answers. Once per outage. | | accounts.connectivity-restored | log | accounts/usage.ts | The connectivity probe (or any real answer) found the network back after an outage (#110), and every tracked account — accounts counts them — is read at once instead of at the end of its clock. | | accounts.workspace.failed | log | accounts/workspace.ts | Provisioning a profile's config dir threw — the skills link, or the workspace-trust write into .claude.json. Fail-open: the session still boots, but it may find no skills and may ignore its permissions.allow entries as an untrusted workspace. | | accounts.workspace.plugins-failed | log | accounts/workspace.ts | Linking the profile's plugin registry to the machine login's threw, so every plugin skill a plan names will be absent from sessions this account boots. It fails open on its own, so the trust write after it still happened. | | accounts.workspace.plugins-linked | log | accounts/workspace.ts | The profile's plugins directory is now a symlink to the machine login's registry, so plugin skills resolve inside sessions that account boots. | | accounts.workspace.plugins-moved-aside | log | accounts/workspace.ts | The empty registry the CLI writes for itself was renamed to a stamped plugins.unused-… so the login's real one could be linked in its place. Moved, never deleted — to is exactly where it went. | | accounts.workspace.presence-failed | log | accounts/workspace.ts | Installing the session-presence hook into a profile's settings.json threw — most often a file that does not parse, which is left for a person. Sessions that account boots stay invisible to the registry until it is fixed; doctor's presence row names the file (control-tower phase 108, #194). | | accounts.workspace.presence-installed | log | accounts/workspace.ts | The session-presence entries — this console's session-hook.sh, and nothing else of the login's hooks — were written into a profile's settings.json (or a stale one refreshed), so every session that account boots reports presence. Written when something changed; every later boot finds them and does nothing (control-tower phase 108, #194). | | accounts.workspace.settings-failed | log | accounts/workspace.ts | Writing the profile's settings.json threw while inheriting the login's plugin keys, so a linked registry can be present and switched off for that account. | | accounts.workspace.settings-inherited | log | accounts/workspace.ts | enabledPlugins and extraKnownMarketplaces were copied from the machine login into a profile that lacked them; keys names which. Written once — every later boot finds them present and does nothing. | | accounts.workspace.settings-unreadable | log | accounts/workspace.ts | The profile's own settings file is not a JSON object, so it was left exactly as found rather than replaced with a fresh one. The plugins stay off for that account until a person fixes the file. | | accounts.workspace.skills-linked | log | accounts/workspace.ts | The profile's skills directory was symlinked to the machine login's, so every skill a boot prompt names exists under that account too. Never overwrites a real directory an operator put there. | | accounts.workspace.trusted | log | accounts/workspace.ts | hasTrustDialogAccepted was written into the profile's .claude.json for each root in roots — the same edit the CLI's error message tells a person to make by hand. Without it a session warns that the workspace is untrusted and ignores its allow-list. | | api.unhandled | log | index.ts | An API handler threw and nothing below it caught, so the request was answered 500 with the error's own message. url and error are the whole record — a bug in the server, not an operator action. | | approval.auto-granted | log | runner/approvals.ts | A tool call that would have raised a card was answered allow by a standing console setting, and the card is born decided so the audit trail still has it. Nobody was asked and no notification went out. | | approval.decided | log | runner/approvals.ts | A pending card was settled — decision is the answer and by is the person, another device, the run ending, or timeout. Since 5.0.0 every ending writes it, from the one settle path (TRS-7) — the timeout used to write nothing — with runId, phase, kind, waitedMs and recovered. Since control-tower phase 131 (#208) also door, when the answer came through a request or the plan's manifest: the press door it proved (local, device, …; console for the manifest) — by is only its label, and a supervisor-chat act is a person's press only when a person's door allowed its card. A timeout answers deny and parks the run rather than letting the session treat a refusal as a verdict. | | approval.expiry-warning | log | runner/approvals.ts | A pending card reached one of its warning leads (control-tower phase 97, #140 — T-15 and T-5 minutes): id, runId, phase, leadMs, expiresAt. The service announces it with the card's consequence. | | approval.extended | log | runner/approvals.ts | A pending card's deadline moved (control-tower phase 97, #140): id, by, minutes, runId, phase, expiresAt, and standsUntil when the extension reached past the hook call's hard limit, where the card will stand instead. | | approval.requested | log | runner/approvals.ts | A tool call is held on the permission hook and a card went out to every open page. It settles on someone's decision or, just before the hook's own patience runs out, on the console's deny. | | approval.resume-failed | log | service-live.ts | A person allowed a standing card and the Resume press it makes for the card's phase threw (control-tower phase 97, #140): id, error. The one-time grant stands; Retry the phase by hand. | | approval.standing | log | runner/approvals.ts | A card extended past its hook call reached the call's hard limit: the session was told no and the card stands (control-tower phase 97, #140) — id, runId, phase, until. | | approvals.persist | log | runner/approvals.ts | Writing the pending-card file — or, with file named, the cards-raised counter beside it — under the instance state directory threw {error}. The cards still work in memory, so the only cost is that a restart loses the outstanding questions, or the count its latest raises. | | approvals.recovered | log | runner/approvals.ts | {count} unanswered cards an earlier console left were judged once the runs could be read (TRS-11): answerable — tool cards whose run's session survived with its token adopted, back in the queue with their detail saying the original call already went ahead or was refused — and unanswerable {reason: n} by session-gone, token-lost, asker-gone or reoffered. Never expired. | | approvals.adoption-held | log | service-base.ts | A boot hold stands, so this console adopted no surviving child's hook token. Under a crash loop that is the point: a console about to die again must not take the only credential that child's next hook call could be answered with. hold names the kind. | | approvals.token-adopted | log | service-base.ts | A run's child outlived the last console, and its hook token was read back out of the settings file it holds and armed again rather than minted afresh (TRS-11) — so its later hook calls are supervised, not unauthorised. runId, slug, pids. | | approvals.token-released | log | service-base.ts | A hook token kept armed past its run's loop was retired, because the last child holding it has exited (#74). runId. | | approvals.token-lost | log | service-base.ts | A run's child outlived the last console but its hook token could not be read back (no settings file, or none this console wrote). Its hook calls reach the console unauthorised and the hook fails open until it ends; its open cards are filed token-lost. runId, slug, pids. | | auth.login-terminal | log | runner/auth.ts | A macOS Terminal window was opened with a console-composed sign-in command already typed, because the OAuth flow needs a real terminal and a web page cannot be one. Any token in command is redacted before it reaches the log. | | boot.autostart-once | log | service-base.ts | This boot spent an autostart: once from the machine profile (FLT-9): spent — the profile's entry became false; unit and disabled — this console's own unit was disabled (launchd's override database, or systemd's login link) so the next login does not start it again. The console runs normally this boot. | | boot.hold | log | service-base.ts | This console booted HOLDING its automation (SHD-5, FLT-9), said once: kind stopped — the stop marker a "stay off" Shut down wrote is still on disk — or autostart-off — the profile says not to start its work unattended; why is the sentence Settings shows. Until it is released nothing is re-adopted, the convergence loop's automatic triggers are off, and no watch clock opens. | | boot.hold-released | log | service-base.ts | The hold lifted, and how says by what. release — an operator's press (Settings, POST /api/automation/hold/release), with the derived actor, the kind and heldSince; a stopped hold's marker was removed and autostart: false still holds the NEXT boot (the profile is not edited). marker-removed — the stop marker vanished from outside under a console holding for it (phase-console start removes it and, finding the job running, starts nothing), with heldSince/heldBy. Either way the held re-adoption and the convergence boot pass run next, once. | | client-root | log | index.ts | At startup, where the browser client is served from: {serving} and the resolved {dir}, or not built yet when no build exists. The check is re-made per request, so a npm run build cuts a live console over with no restart. | | cli-init.persist-failed | log | cli-init.ts | Remembering a session's system/init.claude_code_version for the next console failed on disk; this process still remembers it, so the relay arms for the rest of this boot and a restart reads the version afresh from the next session. {file, error}. | | converge.boot-failed | log | service-base.ts | Startup's one-off convergence work rejected — the stale-worktree sweep, or starting and booting the healer — so no plan was healed at boot. The periodic sweep is the next opportunity. | | converge.failed | log | converge.ts | One healer pass for {slug} rejected, and {trigger} says what asked for it. Nothing was healed on that trigger and the pass is not retried here; the plan waits for the next sweep or request. | | converge.frozen | log | converge.ts | A healer pass for {slug} was skipped because the fleet is frozen by {by} — a pass reads the board through a subprocess and may relaunch a session, so a frozen console does none of it. An operator's own button press still goes through. | | debug.tail-read | log | api/routes.ts | Follow mode on the debug feed asked the index for the rows written since its cursor and the query threw. That pass sends nothing and the cursor is left where it was, so the next tick retries and no row is skipped; a stream repeating this is one an operator is watching stand still. | | debug.tail-write | log | retired 5.2.0 | Folded into sse.write in 5.2.0, when both event streams moved onto the one capped writer (api/sse.ts): the debug tail's own write failure was the same fact under a second name, and a second name is a second place to forget the cap. The row stays so older logs still read. | | degraded | log | lifecycle.ts | A subsystem reported a fault the console survived instead of exiting on. kind names the subsystem and error the fault; the process keeps serving, the health badge turns unhealthy, and stays that way until that kind is cleared. | | degraded.repeated | log | lifecycle.ts | The same subsystem fault fired again and again inside the two-second collapse window and the repeats were folded into one record; times is how many were swallowed. A loud one here means a fault whose own reporting re-triggers it. | | digest.tick-failed | log | service-base.ts | The hourly digest could not be composed (control-tower phase 138, #215): error, and part: turn when only Your turn's counts were unreadable — that digest still goes out, without its first line. Nothing is retried before the next hour. | | env.doctor | log | service-base.ts | The environment doctor found something wrong with how this console was installed or is running — issues carries each finding with its fix. Computed once per process and also shown on the health card. | | env.delivery-channel | log | service-base.ts | The boot doctor's channel row changed (FLT-1): {ok: false} with the {reason} when this console can reach nobody — no subscribed device, no notifier (PHASE_CONSOLE_NOTIFY or the machine profile's notifyCommand), no webhook row, or under --remote Tailscale not running or not serving this port — and the {category} of the first announcement that found no device; {ok: true} when a channel appeared and the push-broken issue was withdrawn. Judged by the prelude's own probeDelivery, so the run-start row and this one agree. | | fleet.freeze-run-failed | log | service-runs.ts | Freeze-all could not freeze one live run and {slug} names it, so that run's lanes may still be executing under a console the banner calls frozen. The rest of the fleet was frozen anyway — check that run by hand. | | fleet.freeze-stand-down | log | service-runs.ts | A run on disk that no live runner owns had its freeze escalation clock removed, so a Freeze-all the banner calls standing cannot be turned into a kill fifteen minutes later by a timer left over from an earlier clocked lane freeze. | | fleet.freeze-unreadable | log | fleet-hold.ts | The freeze marker at {file} exists but its JSON would not parse, so the reader answered "not frozen". If the fleet was meant to be held it is not — inspect the file or move it aside. | | fleet.heartbeat-failed | log | fleet.ts | This console's beat could not be written into its registry row three times running ({id}, {failures}, {error}). Its siblings and every census reader will read it stopped once the last beat is 90 seconds old, although it is still serving — check that ~/.config/phase-console/instances.json is writable. | | fleet.root-gone | log | fleet.ts | The beat found its own root gone ({id}, {skillDir}) — SKILL_DIR/package.json no longer exists under the checkout this console was started from. It asks for a clean, journalled shutdown (SHD-8) right after, which is how a console started from a temporary prefix (a Pro-package test's npm install, #155) stops itself once its checkout is deleted, instead of serving on from a directory nobody can read any more. | | fleet.frozen | log | service-runs.ts | Freeze-all finished: the marker is down, {runs} live runs were frozen where they stood and {converted} stored escalation clocks were stood down. {by} is who pressed it and {at} is what the banner counts from. | | fleet.lane-token-failed | log | fleet.ts | A lane on the machine could not be recorded ({error}), so this admission went ahead under this console's own ceiling only — the machine ceiling (fleet.json maxSessions) did not count it. Check that the state directory's fleet/lanes/ is writable. | | fleet.stopped-write-failed | log | fleet.ts | The clean exit could not be written into the registry row ({id}, {error}), so readers will call this console stopped only once its last beat goes stale, a minute and a half later. | | fleet.thaw-failed | log | fleet-hold.ts | Removing the freeze marker file threw. The caller re-reads it immediately and refuses the thaw if it survived, so the console stays frozen rather than believing a thaw that did not happen. | | fleet.thaw-readopt-failed | log | service-runs.ts | The re-adoption pass that a thaw runs last rejected, so the boot clocks the freeze declined to fire were not re-armed. An overdue wait may sit past its due time until something else pokes the scheduler. | | fleet.thaw-run-failed | log | service-runs.ts | Thaw-all could not thaw one live run and {slug} names it, so its lanes may still be stopped under a console that now calls itself thawed. Thaw that run by hand. | | fleet.thaw-stored | log | service-runs.ts | A frozen run on disk that no live runner owns was woken and its status moved off frozen back to paused. Without this pass a stopped orphan under a standing freeze would stay stopped for ever, because a standing freeze has no clock to end it. | | fleet.thawed | log | service-runs.ts | Thaw-all finished: the marker is gone, {runs} live runs were thawed and {stored} sessions frozen on disk were signalled awake. {frozenAt} is when the hold went down. | | gate.approve | log | service-live.ts | A phase's gate was cleared or revoked through the console. ok is the verdict read back from the engine afterwards, not the script's exit code, so ok: false alongside a zero code means the clearance was written and the gate still does not read clear. | | gate.approve-refused | log | service-live.ts | An approval of a MANUAL gate that was not a person's press — a session's or the supervisor's door, a supervisor-chat act nobody confirmed — was refused before gate-approve.sh ran (control-tower phase 107, #174). Carries slug, phase, by, the actor's via and, since control-tower phase 131 (#208), door — the press door the person test read (isPersonDoor: owner, device, and local on a console with no owner key), which replaced the User-Agent test. A manual gate is cleared only through a person's door: the Gate card, a phone's signed Approve, a confirmed chat act, or gate-approve.sh in a person's terminal. | | owner.door-refused | log | service.ts | A request that PROVED it is an agent's — a session's run token (in Pro also its message token, and the supervisor's own bearer) — pressed a route door-model.js's AUTHORITY_ROUTES names, and the router refused it 403 before any route ran (control-tower phase 131, #208). Carries press (the row), authority (its AUTHORITY_VERBS word), door, proof (run-token, message-token, chat-bearer), label (the run or chat the proof names — never the token) and path. Passes only what the plan's permission.destructive row names for a live phase of that run, by CLI form or Console(<press>). | | policy.owner-key-enrolled | both | service.ts | An owner key — a passkey — was enrolled (control-tower phase 148, #208): the first at the machine through phase-console owner enroll's one-time link, a later one inside an owner session with a fresh touch or through a link the owner minted. Carries keyId (the credential id's first 16 characters — never the key), label, alg (ES256 or EdDSA), origin, by, door and keys (how many the console holds now). Logged on the console, noted on every live run's journal, announced on every subscribed device (health) and kept in the bell for a week. | | policy.owner-key-removed | both | service.ts | An owner key was removed — inside an owner session, with the key touched within five minutes — and every session it had opened ended. The same fields; keys: 0 means the console is back to unenrolled. Announced and in the bell like an enrolment. | | policy.authority-requested | both | service.ts | On a console with an owner key, an authority press through a door the door table does not let make it — local, a device beyond its low and medium answers, a session's token, the supervisor's bearer — was recorded as a REQUEST for the owner (control-tower phase 148): never applied, never dropped. Carries the request's id, press (the AUTHORITY_ROUTES row), authority, risk, door, proof, label (who asked), path and item when the press is about one. Never the body. A repeat of an open request is that request (repeats), not a new line. | | policy.authority-confirmed | both | service.ts | The owner confirmed a request in one press, and the console pressed it through the owner's door exactly as asked: the request's id, press, authority, door, label, by (the owner key's label), status (what pressing it answered) and answer — confirmed, or failed when the route itself refused it. | | policy.authority-declined | both | service.ts | The owner refused a request: nothing was pressed. The request's id, press, authority, door, label and by. | | policy.grant-applied | both | permissions/grants.ts, service-recovery.ts | A grant was applied (control-tower phase 149, #212) — a permission item's Grant, an approval card's Allow with remember, the widen card's Allow, a standing card's one-time allow. Carries the grant's id, by, door, item, wall, rule, scope (call · phase · plan · repository · always), risk, until and changed — EXACTLY what it changed: the hook's cover for one lane, a rule lowered in one run's settings, a policy file's strike, removal or allow rule, a capability switch in the launch unit — with the person's reason and, for a high grant, its blast radius. Logged by the engine; noted on every run the grant reaches; announced (granted). | | policy.grant-ended | both | permissions/grants.ts, service-recovery.ts | A grant ended: how — spent (a call grant's one use), expired (its phase settled, or 24 hours passed) or revoked (a person took it back; one, or Revoke all) — with by, the reason kept, and undone: what a revoke reversed, exactly the row's changed. Noted on every run the grant reached; the settings a lane grant lowered are raised for the run's next child. | | hook.grant-read-failed | log | service.ts | The hook could not read the live grants for a call (control-tower phase 149): the call was judged without them — a deny stands, an ask asks — and nothing was spent. | | hook.person-wait-failed | log | service.ts | The tool approval card could not be recorded as a person wait on the run that asked (Runner.enterPersonWait threw — a runner shape without it). Bookkeeping only: the card still stands and the hook is still answered, because a hook that throws fails OPEN. |

| hook.message-rejected | log | api/routes.ts | A call to the session's messaging door was refused: reason separates a bad token, no run being armed, a caller that is not on loopback, and a session claiming a phase of the run it does not hold. The last is the one worth reading — the token names a RUN, and the phase is checked against that run's own phases[N].sessionId, so phase 3's session cannot send as phase 7. |

| hook.rejected | log | api/routes.ts | A claude child called the approval, closeout or permission-request hook with a bearer token that did not verify, and was answered 401. reason separates a bad token from no run being armed at all — the second is the ordinary shape for a call that arrives after its run ended. On the permission-request hook (phase 14) a call that did not come from this machine is refused 403 first, with reason: not loopback or not local and hook. | | hook.run-stored | log | service.ts | A tool-use hook call carried a token for a run NOTHING here is driving, and a record on disk explained it — so it was answered from that record: its own permissionProfile, its plan's policy overlay and permission.destructive row, its relay state, and a card stamped with its slug and phase (5.2.0, #21 §1). Written once per run, not once per call: the measured orphan made ninety in forty-five minutes. | | hook.run-unknown | log | service.ts | A tool-use hook call carried a run token NO record on this machine explains — not merely one this console is not driving, which since 5.2.0 is answered from the stored run (hook.run-stored). It was still answered, under the strictest guarded profile, rather than being classified against whichever run happens to be current. | | hook.session-rejected | log | api/routes.ts | Session presence was posted from something that is not this machine — not a loopback socket, or in through the remote proxy — and was refused 403. Presence forged from a phone could release a lock a real session holds. | | hook.stop-awaiting | log | service.ts | The Stop hook let a session end its turn with no outcome declared because its own subagents or monitors running in the background are still working (tasks: id, CLI task_type, the tool that started each) — their notification starts the next turn, so the end of the turn is a wait, not an exit. A background shell never counts: it dies with the turn. | | hook.stop-background | log | service.ts | The Stop hook refused a session's EXIT — a partial or complete declared, or a board that already reads done — while its own background subagents or monitors still run (control-tower phase 109, #188): tasks (id, CLI task_type, the starting tool) and blocks, the per-session counter it shares with hook.stop-blocked (after two the guard lets the session go). The session is told to wait for them in one bounded foreground call or TaskStop them and name under Outstanding what each was doing. | | hook.stop-blocked | log | service.ts | The Stop hook held a session's turn: the phase is not closed on the board, or its QA verdict is still owed. blocks is the per-session counter — after two the guard fails open and the session is let go regardless. | | hook.stop-seen | log | service.ts | A supervised session reached its Stop hook and the console matched it by session id to a plan and phase. Its presence in the log doubles as the probe for whether the CLI fires Stop hooks under -p at all. | | hook.permission-request | log | service.ts | A PermissionRequest hook call was accepted (phase 14) — {runId, tool, phase}; its full account is phase.permission-request on the run. | | hooks.installed | log | service-runs.ts | The session-presence hook entries were written into the Claude settings file at {path}; {changed} is false when they were already there. Needs --allow-writes, because the file is outside the console's own state. | | hooks.uninstalled | log | service-runs.ts | The session-presence hook entries were removed from the settings file at {path}; {changed} is false when there was nothing to remove. After this the console no longer sees sessions it did not spawn. | | engine.command | log | shell.ts | One bash engine script the console ran, through the command seam (5.1.0): argv, intent (the script), cwd, ms, code. At debug — PHASE_CONSOLE_DEBUG=engine — except an unexpected non-zero exit, which is info with a tail of what it said, and a timeout, which is warn. Emitted for engine.ts's cached reads and writes.ts's guarded write verbs alike. | | engine.board-persist-failed | log | service-base.ts | The last good board of a plan could not be written beside its runs (last-good-board.json, control-tower phase 84, #96), {slug, error}. The reading still stands in memory for this process; only a restart before the next good read loses it, and then an unreadable board summarises as unknown with no last reading — never as 0 %. | | engine.board-timeout | log | service-live.ts | A plan board read (phase-graph.sh --memory-block) hit its ceiling (control-tower phase 55, #44), at warn: slug, ms (queue wait included — engine.command's ms is the script alone), runMs, load (the 1/5/15-minute load averages), the engine pool's active / queued / max, and served — last-good (a page shows the last good board, marked stale, with its ageMs) or empty (nothing good was ever read). The healer, plan health and the runs still see the timeout, never the stale board. | | git.command | log | shell.ts | One git command the console ran, through the command seam (5.1.0): argv, intent, cwd, ms, code, and truncatedBytes when the output was capped. At debug — PHASE_CONSOLE_DEBUG=git — except an unexpected failure (info, with a tail) and a timeout (warn). The console's nine git helpers all pass through here, so this is the complete record of what it asked git to do. | | git.trace-capped | log | git-trace.ts | A drain of git's own Trace2 event files hit the per-drain fold cap and stopped RECORDING — dir, cap, seen. Written once per drain, never per file. The files past the cap are still deleted: the cap bounds the record, not the cleanup, because a capped tracer that leaves its input behind is the disk problem it was meant to avoid. | | git.trace-write-failed | log | git-trace.ts | The folded git.trace lines could not be appended to their run's .git.ndjson. The raw event files are removed regardless, so tracing costs the record and never the disk. | | retention.swept | log | retention.ts | The retention sweep removed, rotated or trimmed files — count and bytes. One line per sweep, not per file; the sweep runs after sweepOldRuns at boot and once a day. | | retention.oversized | log | retention.ts | A sink is past its cap and was deliberately NOT touched — the ruling ledger (which IS the record and is never pruned) and the console log (which the writer that owns it rotates). Says sink, path, bytes and why, because the honest thing a sweep can do about a file it must not delete is say the number out loud. | | retention.failed | log | retention.ts · service-base.ts | One or more retention actions threw — a file that could not be removed, a log that could not be trimmed. Reported rather than raised: a sweep that stops at the first unremovable file leaves the rest of the disk full. Retried on the next sweep. | | log.level-changed | log | api/routes.ts | POST /api/debug/level raised or lowered what this console admits (5.1.0): the level now in force, the debug channel spec, until (the deadline it reverts at) and by. Turning debug on used to mean restarting with an environment variable, which loses the very run you were looking at. | | log.level-reverted | log | api/routes.ts | DELETE /api/debug/level put the level back at once, with by. An override also lapses on its own at until with no line at all — the deadline is compared against the clock rather than armed on a timer, so there is nothing to fire. | | http.request | log | index.ts | One HTTP request the console answered (5.1.0): method, path, route (the first two path segments — a group, not a path), status, ms, bytes, requestId and agentClass. At debug, because an open page polls — but info whenever status >= 400 or ms >= 1000, so the requests somebody comes looking for afterwards are on record without anyone having turned a channel on first. The request is also a span: requestId IS its span id, so everything the handler did carries the same trace. | | http.stream | log | index.ts | A long-lived response was opened or dropped — /events and /api/debug/tail (5.1.0). phase is open or close, and both halves share one span; the close carries ms, the whole time it was held. Its own event because a stream has no status to report at the end and no length: it is opened and later goes away, and "the phone stopped getting events" is a question about the close. | | http.stream-error | log | index.ts | An HTTP request or response stream errored mid-flight for a reason that is not a client going away — where says which stream, with the URL. Disconnects (a closed tab, a sleeping laptop) are handled silently and never reach this line. | | human-steps.declared | log | human-steps.ts | A human step was written to the ledger and announced (control-tower phase 41): {id, kind, where, birth, slug, phase, pushed} — ids and words, never the step's text. | | turn.guard-refused | log | human-steps.ts | The guard refused a session's declared step at ingest (control-tower phase 130): {slug, phase, kind, rule, exit, sentence, commands?} — no item was raised. Since control-tower phase 135 (#212) rule: G5 too, stage: ingest, from the service: a blocked --needs permission declaration naming a wall nothing recorded for its lane raised no item ("nothing refused this — run it"); the declaration's pre-check refuses the same at the door, exit 4, journalled phase.turn-refused. | | turn.proof-reread | log | service-recovery.ts | A plan item's proof was read from its plan bullet again at check time, because the bullet changed since the item was raised (one-pool phase 13, #345): {slug, phase, stepId, from, to}. The item reads the new proof from this check on — the check's own ledger line carries it — so a plan fixed after launch unsticks the item it raised. | | turn.gate-settled | log | service-recovery.ts | A Your turn item bound to a gate (its proof reads the gate's row, stepGateOf) was settled as Passed because the engine reads that gate clear (one-pool phase 13, #371): {slug, phase, stepId, by} — by the person whose Approve opened it, or watch when the round or the docs watcher found it opened outside the console (gate-approve.sh, an AI session's row). It resumes what waited on it once, as a passed check does. | | turn.press-stale | log | api/routes.ts | A press bound to a run (expectRunId, turn-by-turn phase 5) reached a plan whose run is another one now — a successor, none, or the same run dismissed: {slug, verb, expected, current}. Nothing was pressed; the route answered 409 with the sentence the item shows. | | turn.check-frozen | log | service-recovery.ts | A judgement check was asked for on a frozen console (control-tower phase 134): {slug, by} — no checking session started; the item says the check could not run. | | turn.waiter-added | log | human-steps.ts | Another lane declared an item already open (G7, control-tower phase 130): ONE item with another waiter, no second push — {id, kind, slug, phase} of the lane that joined. | | turn.closed-stale | log | service-recovery.ts | The round's stale pass closed an item nobody needs any more (turn-by-turn phase 7, turn/stale.ts): {id, kind, slug, phase, runId, reason} — its run superseded, resolved or finished, its phase done on the board, or its errand gone. Dismissed by the console with the reason on the item and a console row under Handled by the AI. | | turn.moved-to-successor | log | service-recovery.ts | The stale pass moved a world act (a sign-in, a secret, an operator's act) from a superseded run's lane to its successor's (turn-by-turn phase 7): {id, kind, slug, lanes: [{from, to, phase}], reason}. The item stays open; a console row under Handled by the AI says where it went. | | turn.identity-park-skipped | log | service-runs.ts | An identity change found a stored run a later run of its plan supersedes (turn-by-turn phase 7): {slug, runId, supersededBy} — no park, no errand, no run.identity-changed; the operator's stop stands. | | human-steps.due | log | human-steps.ts | An upcoming step became due (control-tower phase 121): {id, kind, slug, phase, ref, pushed} — phase 0 is the plan's own act, under its ## Operator errands. Ids and words, never the step's text. | | human-steps.fields-dropped | log | human-steps.ts | A declared step offered fields the language does not keep — a link that is not http(s), a code off a device-code step, a credential off a secret-entry step, an unknown where, or auto-open on a step a session declared — {id, kind, birth, dropped}, the field NAMES only. The step is still recorded without them. | | human-steps.guide-refused | log | service-runs.ts | The launch door could not read a plan bullet's guide: FILE (turn-by-turn phase 2) — not a path under the docs root, a link that leads out of it, over the size limit, or refused by the guide grammar (a secret-shaped line, a link that is not http(s)) — {slug, phase, kind, guide, why}, the reason a sentence that never quotes the file. The item is raised without a guide; the read-time composition reads the plan again on every read. | | human-steps.announce-failed | log | human-steps.ts | The push for a new human step threw, {id, error}. The step is still recorded and moves to notified with pushed: false; its inbox row stands either way, because the row is derived from the ledger. | | human-steps.ledger-unwritable | log | service-base.ts | The human-step ledger could not be written, {slug, phase, error}. The declaration still parks the phase as a plain needs-human — a person's turn that could not be recorded must not cost the park it rides. | | human-steps.clock-failed | log | human-steps.ts, service-base.ts | A pass of the human-step reminder clock — or a reminder's push, an expiry's errand, or a step terminal's exit check — threw (control-tower phase 43): {id?, error}. The clock keeps its schedule; the step's ledger line is written either way. Since control-tower phase 136 the clock drives the ROUND (turn/round.ts), and pass names the round's pass that threw — grants, clock, covered, escalate, due, proofs, emit — or supervisor-clear. | | inbox.ack-failed | log | service.ts | Marking one inbox item as seen threw, so the press did nothing and the row is still asking on the next read. {id, error}. | | inbox.ack-ledger-failed | log | service.ts | In a bulk acknowledgement the local acks file took the item, and appending the matching line to the plan's rulings ledger threw. It reads acknowledged on this console only — the rulings endpoint, and any reinstall, will show it unseen again. | | inbox.ack-many-failed | log | service.ts | The bulk write to the acks file threw before any item was annotated, so every id in the batch comes back failed and nothing was marked seen. {count, error}. | | inbox.acks.unclearable | log | inbox.ts | Removing the acks file behind the clear-acknowledgements control threw, so the annotations are still on disk and the inbox goes on hiding what was already seen. | | inbox.acks.unreadable | log | inbox.ts | The per-instance acks file could not be read or parsed — anything but a missing file, which is the normal case and stays silent. It degrades to nothing-is-acknowledged, so every item reappears unseen, and a corrupt file says this on every inbox read until it is deleted. | | inbox.prune-failed | log | service.ts | Dropping acks whose items have stopped asking threw. The file grows towards its 2000-row cap, and an item that went away and came back stays hidden because its old annotation is still there. | | inbox.source-failed | log | service.ts | One source of the inbox threw while the view was being assembled — what names it: runs, accounts, auth, mcp, plans, or gates (the background refill of a plan's gate answers, phase 56). That source contributes nothing and the rest of the view is built anyway, so a missing row here means a source failed, not that the errand is gone. | | inbox.unack-many-failed | log | service.ts | The bulk removal of acknowledgements threw, so nothing was un-annotated and every id in the batch comes back false. |

| issues.reannounce-failed | log | service-runs.ts | The issues clock's re-announce pass threw for one plan (#118), {slug, error}. Nothing is lost: the pass is a ledger read and one appended line per draft, and the next tick tries again; a draft already marked is never said twice. |

| issues.sweep-failed | log | service-base.ts | The idle refresh of the issue estate rejected. Nothing is retried now — the next tick of the sweep clock tries again and the issues page keeps whatever it already had. | | journal.append | log | runner/journal.ts | One line could not be written to the run's own file. It is still returned and broadcast, so the live timeline looks right until a reload replays from disk without it. Usually a full disk or a permissions change under the state directory. | | journal.full | log | runner/journal.ts | The run's file passed its size cap and nothing more is written to it. The run carries on; its timeline simply ends where the file did. | | landing.compose | log | service-live.ts | A landing packet — a bundle and a patch series under the run's own state directory — was built for {slug}, with {ok}, {commits} and {files} saying whether and how big. It touches no ref and no remote; pushing what it produced stays a person's decision. | | lock.debris-released | log | service-runs.ts | The console released a phase claim left behind by a run it knows is dead, as the owner the lock file names rather than by force. ok: false with a code means the script refused and the claim is still standing. | | lock.released | log | service-live.ts | An operator released a phase claim through the console — who held it, when it was taken, and how far its lease ran. forced: true means it was taken from a session that had not finished; debris: true means the session register said the holder was gone while the lease still ran, and session names the one it judged. |

| locks.ledger-failed | log | locks.ts | The lock history ledger (locks.ndjson, #24) could not be appended — said once per process, with file and error. GET /api/locks and the release verb are unaffected: the ledger is a record, never a reason to fail. |

| mcp.catalog.refresh-failed | log | mcp/catalog.ts | Reading the official MCP registry into the catalog's cached copy (<instance state>/mcp/registry.json, one-pool phase 19, #363) failed part way; error, and how many pages and servers it had read. The copy it would have replaced stays and is searched as it is; with no copy the catalog is the curated list alone, and the page says why. The next catalog read tries again. | | mcp.catalog.refreshed | log | mcp/catalog.ts | The official MCP registry was read whole into the catalog's cached copy (#363): how many pages and servers, the ms it took, and whether the walk was complete (false only at its page bound). A copy younger than a day is searched as it is; an older one is read again behind the next search. | | mcp.catalog.registry-unreachable | log | retired 8.0.0 | The registry's own ?search= timed out at 8 s on every query (#363), so 8.0 reads the registry whole into a cache and searches it locally; mcp.catalog.refreshed and mcp.catalog.refresh-failed replace it. The row stays so older logs still read. | | mcp.catalog.write-failed | log | mcp/catalog.ts | The registry copy was read but could not be written to disk; error says why. It is still searched from memory, and the next console start reads the registry again. | | mcp.config.prune-failed | log | mcp/config.ts | One resolved MCP config file could not be unlinked during a sweep; file names it. Harmless leftovers on disk — the next sweep tries the same file again. | | mcp.continue-without | log | service-runs.ts | The operator chose to go on without the MCP servers a run parked for. The run's policy was set to continue and every phase the preflight parked was retried in order; phases lists them. | | mcp.emit-failed | log | service.ts | Listing the servers for the browsers threw, so no registry update went out over SSE and open pages show a stale server list until the next change. The registry itself is unharmed. | | mcp.health.tick-failed | log | service-base.ts | The periodic MCP health probe threw, so the cached statuses simply go stale. Nothing is retried before the next tick, and the cards show their age. | | mcp.health.unknown-status | log | mcp/health.ts | A probe returned a server state this console has no word for; status is the raw string. It is read as unknown, so the card says nothing rather than something false — worth reporting if a CLI upgrade introduced a new state. | | mcp.login.finished | log | service.ts | A guided MCP sign-in ended (one-pool phase 19, #372): its server, the step it ended on — connected, failed or timed-out — the CLI's exit and the ms since it started. The sheet shows the same ending, with the step a failure stopped on and the CLI's last words. | | mcp.login.pasted | log | service.ts | The address a browser landed on was pasted back into a guided sign-in's terminal for server (#372) — a console reached from elsewhere, where the CLI ran with --no-browser. The address itself is never logged. | | mcp.login.started | log | service.ts | A guided MCP sign-in started (#372): its server, its mode (embedded — a terminal the console runs; external/command — the person runs it) and whether it is remote (the person is not at the machine, so the CLI prints the page and takes the address back instead of opening a browser here). | | mcp.login.bridge-failed | log | mcp/login.ts | The Claude CLI refused to register the temporary server definition a sign-in needs, so the sign-in terminal has nothing to resolve and the flow is abandoned. detail carries the CLI's own words. | | mcp.login.complete-failed | log | service-base.ts | Finishing a server sign-in threw after its terminal exited — taking the temporary definition back, re-probing, or unparking the phases waiting on that server. The card keeps whatever the last probe said. | | mcp.login.release-failed | log | service.ts | A temporary definition written for a sign-in could not be taken back after the server came up connected. Fire-and-forget by design, so reading the server list still works — but a stray entry may be left in the CLI's registry. | | mcp.login.unbridge-failed | log | mcp/login.ts | The claude mcp remove that undoes a sign-in bridge exited non-zero. Best effort by design — a leftover registry entry is a tidiness problem, never a failed flow. | | mcp.park-heal-refused | log | service.ts | A server reconnected and the phase parked on it could not be retried — normally because the phase is claimed elsewhere. The park stays exactly as it was and the operator's own Retry still works. | | mcp.park-healed | log | service.ts | A server a phase parked waiting for is connected again, so that phase was requeued and the operator told on the same channel that announced the park. slug, phase and server say which. | | mcp.probe.failed | log | mcp/index.ts | The probe that asks the Claude CLI about every registered server failed as a whole, so no status was folded in. The last known answers stand, shown with their age. | | mcp.probe.start | log | mcp/index.ts | The MCP health probe started a one-turn claude -p (control-tower phase 100, #73): its own event and its own budget (PROBE_STARTS_PER_HOUR, probeCeiling) — never a session.start and never counted against the work-start ceiling, because a probe is not a session. The actor whole (door, trigger, guard, counter) and how many servers it asks about. | | mcp.probe.ran | log | mcp/index.ts | The MCP health probe ran — its cadence on the record (SLF-2): which door (mcp-health-probe on the five-minute clock, mcp-boarding-preflight when a boarding found the cache stale), the ms it took, how many servers it asked about, and the error when it could not answer. | | mcp.probe.refused | log | mcp/index.ts | The start ceiling refused the probe: the cache goes stale until the window rolls on, which is what it does overnight anyway. | | mcp.probe.sigkill | log | mcp/health.ts | The probe ignored SIGTERM through its grace and the group was SIGKILLed — an npx stdio shim swallowing the TERM, most likely. The CLI itself leaves on the TERM and runs its SessionEnd hook, which is what the grace is for. | | mcp.refresh-failed | log | service.ts | The re-probe fired right after a server was registered threw, so the new server keeps the status it had rather than being resolved now. The next health tick picks it up. | | mcp.registry.unreadable | log | mcp/store.ts | The server registry file exists but could not be read or parsed. It degrades to empty, so the console behaves as though nothing is registered — inspect the file before adding servers back, because a write will overwrite it. | | mcp.require-timeout | log | service.ts | A phase parked under the require policy waited out the operator's timeout, so it was released to run without the servers it asked for and an errand naming them was journalled. servers and by say which servers and what released it. | | mcp.require-timeout-failed | log | service.ts | Releasing such a parked phase when its clock fired threw. Nothing was journalled and the park stands, so the phase waits until an operator acts on it. | | mcp.require-timeout-frozen | log | service.ts | The wait clock came due while the fleet is frozen, so the phase was NOT released — re-boarding it is an auto-start. The park record and its timestamp stay on disk and the thaw pass finds the clock overdue. | | mcp.tools.changed | log | mcp/index.ts | A connected server is offering a different set of tools than when it was last seen; added and removed say how it moved. Worth an eye — a supply-chain change arrives looking exactly like this. | | mcp.vars-filled | log | mcp/index.ts | A value was given to each ${VAR} a server's configuration writes (Fill it in, phase-console mcp fill; one-pool phase 19, #363): the server and the variable names in vars, never their values, which are held where a secret is and spliced into the server's command when a session starts. | | notifications.append | log | notifications.ts | A notification could not be added to the store on disk, so it is live on the page and in memory only and will be gone after the next boot. {path, error}. | | notifications.mkdir | log | notifications.ts | The notifications directory could not be created when the store was built. Nothing persists for the life of this process — the store still works, purely in memory. | | notifications.rewrite | log | notifications.ts | The compacting rewrite failed — retention, a mark-read, or a burst of deliveries collapsed into one write. The temp file is removed and the old one left as it was, so rows that were meant to be pruned come back on the next load. | | notify.failed | log | runner/approvals.ts | The operator's own out-of-band notifier, the command in PHASE_CONSOLE_NOTIFY, could not be spawned or exited non-zero. That one notification reached nobody and nothing else was affected — a broken notifier never stops a run. | | outcome-inbox.apply-failed | log | service-runs.ts | Acting on a declaration from a session the console did not spawn rejected. The file is set aside under outcomes/ignored/<name>.failed and journalled phase.outcome-ignored {reason: failed} (since 5.0.0 the file is consumed only AFTER the act settles, WAI-7): the phase keeps whatever state it had, and a person can read what was said. | | outcome-inbox.board-failed | log | service-runs.ts | A declaration asked for a re-board and starting that run threw, so the boarding hint is on disk but no session was launched. The healer's next pass is what picks it up. | | outcome-inbox.board-frozen | log | service-runs.ts | A declaration that would have re-boarded {slug} phase {phase} arrived while the fleet is frozen by {by}. The declaration itself was still recorded — a frozen console hears what a session says about itself and starts nothing — and only the launch was withheld until a thaw. | | outcome-inbox.declaration-refused | log | service-runs.ts | A declaration from a hand session for {slug} phase {phase} was recorded and not acted on — {status} past its cap, or a second partial inside the cooldown ({why}, {count}). The journal's phase.declaration-refused has the arithmetic; Retry the phase to clear the count. | | outcome-inbox.declared | log | service-runs.ts | A session the console never spawned wrote its own result for {slug} phase {phase}: {status}, plus {sessionId} when it named one. What happens next depends on that status and on whether a runner is live for the plan. | | outcome-inbox.rejected | log | service-runs.ts | An inbox file failed validation — unparseable, the wrong version, an unknown status, or a {slug} and {phase} that do not match the name it was filed under. Since 5.0.0 it is set aside under outcomes/ignored/<name>.invalid and journalled phase.outcome-ignored on the plan's latest run, never deleted (WAI-7); look at the writer if it repeats. | | outcome-inbox.resume-refused | log | service-runs.ts | A declaration that would resume {sessionId} arrived while that session is still running (or an unexpired lock names it), so the file is KEPT and nothing is armed; the sweep reads it again and acts once the session ends. Said once per declaration. | | outcome-inbox.stale | log | service-runs.ts | A declaration was read and found older than the inbox's maximum age (24 h); {writtenAt} says how old. Since 5.0.0 it is set aside under outcomes/ignored/<name>.stale and journalled phase.outcome-ignored, never deleted (WAI-7). Usually a file left behind by a session that ran while this console was away for a long time. | | outcome-inbox.sweep-failed | log | service-runs.ts | The periodic floor sweep of the session inbox threw. The directory watcher is the fast path and is untouched, but anything whose watcher event was dropped now waits for the following sweep. | | outcome-inbox.watch-failed | log | service-runs.ts | The recursive watcher over {dir} could not be created, so nothing is picked up the moment it lands. The periodic sweep is the only path left and declarations are seen up to one interval late. | | outcome-inbox.watcher-error | log | service-runs.ts | The inbox watcher errored and was closed and forgotten, dropping the fast path for the rest of this process. The periodic sweep still finds what sessions write, one interval later; only re-arming the inbox brings the watcher back. | | boot.adoption-hold | log | crash-ledger.ts | The latch every writer of the orphan park consults was set or cleared. While it is set, reconcileRun's read path parks nothing as orphaned-session and recoverApprovals adopts no token — the two acts a crash-looping console performed on every boot against runs that were still working. held, reason. | | crash-ledger.write-failed | log | crash-ledger.ts | This boot could not append to crashes.json. The console carries on: a ledger that cannot be written must never be a console that cannot start, but the breaker cannot count a boot it did not record. | | previous-run-crashed | log | crash-ledger.ts, index.ts | At boot the console found the per-boot marker (boots/<pid>.json in its state directory) of a console before it whose process is gone: SIGKILL, an out-of-memory kill or another hard stop rather than a clean exit, which removes its own marker (control-tower phase 48, #72). Written once per crash found, with that console's pid, startedAt and port, and each one also lands on the crash ledger (crashes.json), where the inbox row and the crash-loop breaker count it. Independent of how much the log says — the old verdict read the last 400 log lines and missed any console that died after about an hour of logging — and a launch that never owned the port writes no marker and finds nothing of its own. | | boot-marker.write-failed | log | crash-ledger.ts | The console owns its port but could not write its per-boot marker (file, error). It carries on, but the next boot cannot tell whether this one ended hard. | | process.loop-stall | log | runtime-probe.ts | A one-minute window closed whose worst event-loop delay was at least a second — maxMs, p99Ms and windowMs say how bad and over how long (control-tower phase 56, #75). Everything this console answers waited behind it: pages, hooks, the lease keepalive. The same window is the phase_console_process_event_loop_delay_max_seconds gauge. | | queue.policy-set | log | service-runs.ts | A person chose the scheduling policy or the load guard's factor (control-tower phase 100, #135 F.24, G.27): policy for the console, or for one plan when slug is set (null gives the plan back to the console's), and/or loadFactor (0 switches the guard off) — with the actor and its reason. Stored in the console's preferences and read by the next scan. |

| queue.ordered | log | service-runs.ts | One Queue move (one-pool phase 39, #380), logged once for all the phases it moved: the actor and reason, moves, the check's verdict (yes or yes-with-effects — a no is refused before anything is written) and the order before and after. Each moved phase's run carries phase.queue-ordered. | | queue.reverted | log | service-runs.ts | A revert of Queue overrides (one-pool phase 39): the actor, the slug and phase it named (none: every override), and the phases it reverted. | | queue.order-lapsed | log | service-runs.ts | A Queue override lapsed because its anchor settled (one-pool phase 39): the phase, its anchor and why. |

| press.outdated | log | api/routes.ts | A write came from a page an earlier release served (one-pool phase 18, #373): its x-build-rev header names another build than the one this console serves, so it was refused with a 409 (outdated: true, "This page is from a previous release of the console — reload it, then press again.") before the door check and before any route: path, page and console (each build's first 12 characters). Only a page sends the header, and a build either side cannot name is never refused. | | push.action-key.malformed | log | push/actions.ts | The notification-button signing key on disk decoded to {bytes} bytes instead of 32, so a fresh one was minted over it. Harmless by design, unlike the VAPID key: the only cost is that buttons on notifications sent earlier stop working and open the app instead. | | push.action-key.unreadable | log | push/actions.ts | The notification-button signing key could not be read for a reason other than the file being absent — {code} is the errno — so a fresh one was minted. Only the buttons on already-sent notifications are lost. | | push.action-key.unwritable | log | push/actions.ts | A freshly minted notification-button signing key could not be saved to {file}. It works in memory for the life of this process, so the only cost is that a restart invalidates the buttons of anything sent before it. | | push.dropped | log | push/index.ts | A device hit the failure ceiling (MAX_FAILURES consecutive undelivered pushes) and its subscription was deleted; {failures} is the count that spent it. That device gets nothing more until someone re-subscribes it in Settings, Notifications. | | push.gone | log | push/index.ts | A push service answered 404 or 410 for a device and the subscription was forgotten. This is the normal end of a subscription's life — the app deleted, the browser profile wiped, permission revoked — and needs no action. | | push.health | log | service-base.ts | One push service has rejected this console's sends {streak} times in a row for the same {reason}, across the devices in {devices}. A push-broken card went on the environment panel and an out-of-band notice went out, because the channel under suspicion cannot carry its own obituary. Usually a skewed machine clock or a device subscribed under a different signing key. | | push.health-cleared | log | service-base.ts | The push-broken rows a failed push raised were withdrawn (one-pool phase 18, #280): cause — delivered (a push service accepted a push, an announcement's or Send a test's; service names it, and only that service's rejection rows go with the undelivered row) or devices-changed (a device was added, removed or re-subscribed: every such row goes) — and the issues ids (push-undelivered, push-reject:<service>\|<reason>). The row comes back if the fault does; the out-of-band notice stays once per process. | | push.no-device | log | service-base.ts | An announcement found no device at all to send to — none is subscribed — and {category} is the first category that met it, named once per process (FLT-1). The delivery record carries a no-device row, and the channel row on the environment panel names the category when no notifier or webhook reaches anyone either. | | push.persist-failed | log | push/index.ts | The device list could not be written to disk. Notifications keep working for the life of this process, but the most recent subscribe, unsubscribe, category or quiet-hours change will not survive a restart. | | push.quiet | log | push/index.ts | One device's quiet hours were set or cleared; {quiet} is the window, or off. Inside that window announcements for that device alone are skipped and recorded in the delivery ledger rather than sent, unless the message is urgent and the window allows urgent ones — a person's turn's reminder never is (control-tower phase 138). | | push.quiet-migrated | log | service-base.ts | The reminders' own quiet hours (reminderQuiet, retired in control-tower phase 138, #215) were moved onto the devices — from a config written before the change, at boot, or from a page that still sent the key: {quiet} (the window, or malformed — dropped), {devices} that took it (each with no window of its own) and {kept} (devices with their own, left as they were). The key is then gone from the config, so this is written once. A warn with {error} means the move failed and nothing changed. | | push.ready-failed | log | service-live.ts | Composing or sending the "a phase became ready" announcement threw. Only the notification is lost — the boards are unaffected — but a readiness already folded into the snapshot will not be announced a second time. | | push.rejected | log | push/send.ts | One send came back with an error status from {endpoint}: {status}, the first 200 bytes of the body in {detail}, and a parsed {reason} such as BadJwtToken when Apple or FCM named one. One line per rejection; the streak that means the channel is broken is classified upstream. | | push.subscribed | log | push/index.ts | A browser registered a new device for notifications — {label} on {service}, with site (the origin it subscribed under) and deviceId (the paired device it belongs to) when the caller that registered it said. A repeat from the same endpoint updates the existing row instead and says nothing here. | | push.throttled | log | push/index.ts | A push service answered 429 for a device; {retryAfter} is the seconds it asked for when it gave any. This send is not retried and no failure is counted against the device. | | push.undelivered | log | service-base.ts | One notification reached none of its devices — {category}, {title}, and every device's outcome in {devices}. A push-broken card goes on the environment panel once per process; send a test from Settings, Notifications to see whether the channel works at all. | | push.unreachable | log | push/index.ts | A send never left this machine: no network, DNS down, the laptop asleep. Deliberately not counted against the device's strike budget and not fed into the per-service rejection streak, because the browser subscription is still perfectly valid. | | push.unsubscribed | log | push/index.ts | A device row was removed, by the operator in Settings or by the console after a permanent rejection. {key} is the first 40 characters of whatever identified it — an id or an endpoint. | | push.vapid.created | log | push/vapid.ts | This instance had no push signing key and minted one into {file}. Each console keeps its own pair on purpose, and a browser's subscription is bound to whichever key was current when it subscribed. | | push.vapid.damaged | log | push/vapid.ts | The push signing key file was read and its content is wrong — {why} says how — so push is off for this whole process. Deliberately not replaced, because minting a new key silently unsubscribes every device. Inspect the file, or move it aside and re-subscribe each device. | | push.vapid.raced | log | push/vapid.ts | Another writer created the key file between this process's read and its write, so the pair minted here was discarded and theirs read and used instead. Harmless — nothing minted here was ever published to a device. | | push.vapid.unreadable | log | push/vapid.ts | The push signing key file could not be read at all, on two attempts, so push is off for this process. The file is deliberately left untouched: it is probably intact, and moving an intact key is exactly what unsubscribes every device. Restart, and check for file-descriptor or disk trouble. | | push.vapid.unusable | log | push/vapid.ts | The key file is well shaped but the crypto layer refused the stored JWK, so push is off instead of the whole service failing to construct. Inspect the file; if it cannot be repaired, move it aside and re-subscribe each device. | | push.vapid.write-failed | log | push/vapid.ts | A freshly minted push signing key could not be written, so push is off rather than running on a key that changes every boot — and each boot would unsubscribe whatever the last one subscribed. Make the push state directory writable and restart. | | qa.activate | log | service.ts | The QA gate was switched on for a plan from the console, backfilling finished phases as waived. ok and mode are read back from the engine rather than from the script's exit code, which is 1 in the normal case. | | qa.mode | log | service.ts | A QA regime was written for a plan, or for one phase of it. directive is what was asked for, plan and regime are what the engine reads back afterwards — they differ legitimately, for instance when off over an existing ledger reads as waived. | | qa.waive | log | service-runs.ts | A phase's owed QA verdict was set aside from the console, with a mandatory reason and the next round number. ok is the verdict read back — only when it is true did the gate stop holding that phase's dependents. | | recovery.sync-failed | log | service-base.ts | Writing a recovery session's verdict back onto the run record threw. The operator is still notified, but the run can be left sitting halted with the phase unflipped — check the board and settle it by hand. | | registry.backup-failed | log | registry-file.ts | A newer-versioned registry file (registry names which — accounts or MCP) was about to be rewritten by this older console and the copy-aside failed; the write proceeds anyway, so the newer file's extra fields are lost. | | registry.downgrade | log | registry-file.ts | A registry file on disk carries a version newer than this console writes (registry names which — accounts or MCP): the original was copied to a .v<N>.bak beside it before the older console rewrote it, one backup per version. | | relay.bookkeeping-failed | log | relay.ts, service-base.ts | Something around a relayed answer failed and the answer stood regardless — what says which: park (the needs-human park of an unanswerable question), announce, ruling (the ledger row), tell (the notice down the session's stdin) or defer (the shutdown's deferral). A hook that throws fails open, so none of these may cost the answer. | | relay.failed | log | relay.ts | Relaying a question threw; the hook was answered by the policy table instead (holdQuestion), exactly as on a run with no relay. {error}. | | relay.kept-answer | log | relay.ts | A question the console had already answered was asked again and got the kept answer at once — a deferred call its session resumed (deferred: true), or a question answered at boot. {runId, phase, approvalId, mechanism}. | | relay.persist-failed | log | relay.ts | The relay's own state — the question keys each phase has asked and the answers kept for a resume — could not be written. The live answers are unaffected; a restart may forget a kept answer or a repeated key. {file, error}. | | relay.recovered | log | relay.ts | A question card an earlier console left open was answered at boot (TRS-11, AC-6): {approvalId, runId, phase, deferred, answered} — answerable when it was deferred, hook-closed when not, never expired. | | restart.failed | log | lifecycle.ts | The registered handler threw, so the process is still running and the API answers the button with a refusal. {reason, error}. | | restart.reexec | log | lifecycle.ts | A console running with no launchd unit over it spawned its own successor, detached, after closing its listener and draining. {command, pid} names the process that should be answering on the port a moment later. | | restart.reexec-failed | log | lifecycle.ts | Spawning that successor threw. This process exits anyway, so nothing is left serving the page and somebody has to launch the console by hand. | | restart.requested | log | service-base.ts | Somebody asked the console to go down and come back, and the readiness check let it through. The actor is DERIVED from the request (SHD-3), never supplied: by (the body's label, else the proxy's login, else operator for a browser or the CLI and script for anything else), via (api or cli, by User-Agent class), origin (local, or the hostname the proxy served) and remoteUser; then supervisor and force — with supervisor: 'none' the process re-execs itself, otherwise its unit brings it back. force only skips the supervision check, never the in-flight one. | | restart.journal-failed | log | service-base.ts | Writing a restart's line onto a run's own journal (run.restart-requested, run.restart-held, run.restart-released) threw; the restart carries on. | | review.comment | log | service-live.ts | A comment on a phase's diff was added, deleted, marked answered or reopened; action says which and comments how many the phase now carries. | | review.record | log | service-live.ts | A verdict was set on a phase's diff, and by whom. requested-changes holds every dependent phase from boarding until somebody approves it or takes it back. | | review.send-back | log | service-live.ts | A reviewed phase went back to work with its unresolved comments composed into the instruction. boarded: false means the run is not driving, so it waits until someone starts or resumes it. | | review.withdraw | log | service-live.ts | A phase's verdict was cleared, so nothing is held by it any more. had: false means there was none to clear and the call changed nothing. | | rulings.ack-unattributed | log | runner/rulings.ts | An acknowledgement with nobody named was refused rather than written (every ack names who saw the ruling since 5.0.0 — the route derives the actor; a script names itself). Nothing changes; the ruling stays unseen. | | rulings.ack-failed | log | runner/rulings.ts | Appending an acknowledgement line to a plan's ledger failed, so the decision stays unseen there even though this console's own acks file took it. Annotation only — nothing about the run changes. |

| runner.landing-failed | log | runner/runner-loop.ts | The landing engine threw in one half (local, remote, resume) and the runner caught it: the lane, the grant and the loop carry on, and the journal's phase.landing-failed carries the same words. | | scheduler.fleet-scope | log | runner/scheduler.ts, pro/scheduler/fleet-scopes.ts | A queued entry is held by ANOTHER CONSOLE's live grant on this machine (many-plans-one-repo phase 9): a claim under <stateHome>/fleet/scopes/<instanceId>/, written at that console's grant with its scope, branch, tree and repo, read here by the same scope arithmetic the locks get and lapsed with that console's heartbeat. {slug, phase, runId, holder: {instance, name, slug, phase, runId, scope}}, said once per (entry, claim); the entry waits behind a fleet holder naming the console. Also action: write-failed from the claim writer when this console's own claim could not be written — the per-console scan still holds. | | messages.ack-unattributed | log | pro/messaging/messages.ts | An ack arrived naming nobody and was refused rather than written. A sender reads an ack as "my peer has this", so an ack with no one behind it says less than none. | | messages.append-failed | log | pro/messaging/messages.ts, service-runs.ts | The console could not append to a plan's mailbox — a message, a delivery state or an ack. Never thrown: the mailbox is a conversation and losing a line is not worth failing the request it rode in on. {file, error}. | | messages.read-failed | log | pro/messaging/messages.ts, service-runs.ts | A plan's mailbox exists and could not be read — a missing file is the ordinary state and stays silent. It degrades to an empty mailbox, so the run page shows the plan's sessions as having said nothing to each other. | | issues.append-failed | log | pro/issues/drafts.ts, service-runs.ts | The console could not append to a plan's issue ledger — a draft's state line, the comment it derived from a duplicate, or a person's edit — {file, error}; or the phase.issue-* line about a draft could not be written to the run's journal, {slug, event, error}. Never thrown: a state that did not reach the file moved for this pass only — the next read finds the draft where the ledger left it and decides it again — and an edit that did not is still answered ok. | | issues.read-failed | log | pro/issues/drafts.ts, service-runs.ts | A plan's issue ledger exists and could not be read — a missing file is the ordinary state and stays silent — so this read degrades to an empty ledger, {file, error}; or the sweep the watcher's debounce or the boot pass ran for {slug} rejected, {slug, error}. Nothing is retried until the file changes again or the next boot. | | issues.labels-created | log | pro/issues/labels.ts | Filing an issue needed a standard label the repository lacked (control-tower phase 115): {repo, created, failed} — the labels gh label create made (never --force, so a label a person coloured is never repainted) and, with why, the ones it could not, which that filing then goes without. A repository's labels are listed once per console process; a repository with everything already is silent. | | issues.author-started | log | pro/issues/author.ts | A plan is being authored from issues (control-tower phase 120): its ONE unattended session has been asked for — {job, door, repo, numbers, slug, preset, model, effort, account}. door is chat (the supervisor chat's plan-issues) or solve (Solve with autopilot); model/effort are the AUTHOR's pair (phase 119's authorModel/authorEffort, else the chat's), null for the machine's default. The session runs as PE_SESSION_KIND=author and writes into a scratch docs root only. | | issues.author-retried | log | pro/issues/author.ts | The console's checks refused an authored plan's first draft and sent it back to its session ONCE with their findings, word for word (one-pool phase 27, #310) — {job, slug, findings, resumed}. findings is how many; resumed says the retry resumed the first session rather than starting a fresh one with the prompt again. A plan still red after the retry settles draft. | | issues.author-draft-install | log | pro/issues/author.ts | A person pressed Check and install on a draft (one-pool phase 27, #310) — {job, slug, by, edited, findings}: the draft, as the console held it or as they edited it, went through the console's checks again in a fresh scratch root. findings is how many came back; zero installs the plan (and starts a Solve job's run), any other number leaves it a draft with the new findings. No session is spawned. | | issues.author-ended | log | pro/issues/author.ts | That job settled (control-tower phase 120): {job, door, state, slug, repo, numbers, costUsd?, findings?, runId?, error?}. state is installed (the console's own lint, validate.sh and verify review were green; the plan is in docs/plans/, its issues snapshotted beside it and relabelled plan:<slug> under --allow-issues), draft (red — findings counts what the console found; nothing installed), failed (no plan came back, or it could not be installed over an existing one), and for Solve started (runId: the run its door, issues-solve, started — a run.start naming that door is on the run's journal) or start-refused (the plan stays installed; error says why — a fleet hold, the start ceiling, the prelude). | | issues.operator | log | service-runs.ts | One line per drafts-engine event on the OPERATOR ledger (control-tower phase 12, #30) — {event, …payload}, the phase.issue-* name and what it carried. An operator's draft has no plan and no run to journal on, so the ledger itself is its record and this line is the instance log's copy; the inbox row and the announcement speak for it the way they do for a session's draft. | | messages.socket-failed | log | pro/messaging/socket-send.ts | A write to a session's CLI inbox socket failed for a reason that is not routine. ENOENT and ECONNREFUSED — a socket whose session has ended — are unreachable and deliberately silent, because one line per ended session would bury this. | | messages.tokens-read-failed | log | pro/messaging/auth.ts | The per-run messaging bearers could not be read back at boot, or the file was torn. It degrades to no tokens, which costs 401s on every send until the next boarding re-mints — never a console that will not start. | | messages.tokens-write-failed | log | pro/messaging/auth.ts | A minted bearer could not be persisted. The token still works for this console's life; a restart will not recognise it, and every session holding it would then 401. |

| rulings.append-failed | log | runner/rulings.ts | The console could not append a ruling of its own — a relayed question's answer (phase 14) — to a plan's ledger. The answer itself stands and is journalled; only the ledger row is missing. {file, error}. | | rulings.boot-ingest | log | service-runs.ts | Folding one plan's ledger into its run threw during the boot sweep or the periodic floor that follows it. That plan's recorded decisions are not on the run's timeline yet; the next sweep tries again. {slug, error}. | | rulings.ingest-failed | log | service-runs.ts | The same fold, this time fired by the file watcher after a session recorded a decision. The line is safely on disk and the periodic sweep will pick it up, so this is a delay rather than a loss. | | rulings.remembered | log | service.ts | A keyed ruling became a standing answer — for its plan (a ## Decisions row, source ruling, through decisions.sh promote) on this console (policy.<key>), or — a relayed answer, since phase 14 — as a relay rule (scope: rule, the console's relayRules) — and was acked in the ledger by the person or script that remembered it. | | rulings.read-failed | log | runner/rulings.ts | A plan's ledger file exists and could not be read — a missing file is normal and stays silent. It degrades to an empty ledger, so the inbox and the run page show that plan as having recorded no decisions at all. | | run.press-continue-failed | log | service-runs.ts | The continue a person's press queued behind the run's loop — boarding the phase the person slot launched once the loop ends — failed to start: slug, phase, error. The phase stays pending with its hint for the next start. | | runner.automatic-park-off | log | runner/runner.ts | A lane waiting inside its turn would have been parked by the watchdog, and stallAutomaticPark is off — the stall card stands and nothing is parked. Once per episode. | | runner.crashed | log | runner/runner-loop.ts | The drive loop itself threw, which would otherwise be an unhandled rejection and a console that disappears. The run is halted with the error as its reason and the lanes are drained. Always a console bug, not a phase going wrong. | | runner.freeze | log | runner/runner-control.ts | Stopping one lane's process group failed, so that lane is NOT held and the freeze skips over it. {pid, error} — the other lanes did stop, which leaves a run half-held and an operator two answers to a single question. | | runner.git-probe | log | runner/runner.ts | The probe behind the run's checkout card — branch, ahead and behind, dirty — threw. The card keeps whatever it last said and the run is untouched; usually a repository that moved under the console or a git that will not run in that tree. | | runner.halt | log | runner/runner.ts | The run stopped for a reason no single phase owns. Its queue is withdrawn, sleeping lanes are woken to stand down rather than spawn another attempt, and the status word stays halting until the last lane settles. {slug, runId, reason, phase}. | | runner.ladder | log | runner/runner.ts | Climbing the recovery rungs for one stuck or failed phase threw; the other candidates in the same pass still climb. The phase's fingerprint was already remembered, so nothing is tried on it again until its record actually changes — a phase that silently stops being retried was seen here first. | | runner.gate-reread-failed | log | runner/runner-loop.ts | Re-reading a held phase's gate threw (one-pool phase 23, #239); the phase stays gated with its note, and the next tick reads it again. | | runner.lane-unknown | log | runner/runner-base.ts | Asking whether the phase has a registered worktree threw. Never a reason to refuse a session: it runs in the shared root, which is where all of these ran before per-phase trees existed. | | runner.lane.crashed | log | runner/runner-loop.ts | One phase's promise rejected inside the runner instead of returning an outcome. That phase is halted with the error as its reason and the run stops carrying on past it. | | runner.lease-refresh | log | runner/runner.ts | Renewing a phase's lock lease threw before the script could answer, so neither a refreshed nor a lost line was journalled and the timer simply tries again. Repeating means the lease is ageing out, and when it expires another session can claim that phase. | | runner.liveness | log | runner/runner.ts | Evaluating one lane's stall signals on the liveness tick threw. That lane is skipped for this tick and the others are still evaluated, so a nudge or a park lands a tick late rather than never. | | runner.local-job-nudge-refused | log | runner/runner.ts | A lane waiting on a long job of its own was to be nudged and the write into the child's stdin did not land. Said once per episode, not once a tick; no rung is counted, so the park at the local budget is still what ends it. | | runner.local-job-watchdog-frozen | log | runner/runner.ts | A lane sitting on its own long job would have been nudged or parked, and the fleet is held, so nothing was done. {by} names who holds it — thawing lets the remedy resume. | | runner.mcp-config | log | runner/runner-attempt.ts | The per-run MCP config file could not be written. Never fatal: the phase is boarded without those servers, which is worse than running with them and better than not running at all. | | runner.mcp-degraded | log | runner/runner-loop.ts | Some of the servers the phase asked for are unreachable or unusable; summary names each one and why. The phase is boarded attached to the rest, so it runs with less than it asked for. | | runner.no-approvals | log | runner/runner-control.ts | No approval broker or origin is configured, so no settings file is minted for this run. Its sessions run without the console's approval hook — nothing can be asked of a person mid-phase — and without the deny list that file carries, leaving only whatever rules the checkout's own settings hold. | | runner.note-spend-failed | log | runner/runner-base.ts | Recording a session's spend as proof the account can pay threw (#78). The spend is booked regardless; only the wall lift it would have made is lost until the next reading. | | runner.park-poke-frozen | log | runner/runner.ts | A park's window elapsed and the poke that would have woken the drive loop was dropped because the fleet is held. The park itself is untouched — the phase record still carries its own clock — so the thaw's poll picks it up; only promptness is lost. | | runner.parked | log | runner/runner-control.ts | The run stopped and said a person is needed, without calling it a failure — the case it exists for is an approval nobody answered. The failure budget is not charged and the phase can be retried the moment someone replies. {runId, slug, reason, phase}. | | runner.permission-profile | log | runner/runner-control.ts | Somebody widened or narrowed what this run's sessions may do, from one profile to another. The settings file is rewritten, so it binds from the next phase; the live child keeps the argv it was launched with, though the approval hook reads the new profile on its very next call. | | runner.persist | log | runner/runner.ts | Writing the run's checkpoint to disk threw, on either the debounced or the durable path. The run carries on from memory, but a console that dies now comes back to a stale file — phases, lanes and queue as of the last write that worked. | | runner.phase-halted | log | runner/runner.ts | One phase settled with an ending of its own and the run went back to its other candidates. kind is the class the recovery classifier reads. Enough of these in a row trips the failure streak, which is what stops the whole run. | | runner.relaunch-refused | log | runner/runner-control.ts | An automatic relaunch of a run whose failure streak is spent was refused at the door (RCV-3); the run's journal carries run.relaunch-refused with the actor. {runId, slug, door, consecutiveFailures}. | | runner.plan-hold-failed | log | runner/runner.ts | Writing the needs-human --needs plan-approval declaration for a held plan failed; the hook still denied the call, so the session ends its turn with no declaration and its ordinary closeout path applies. {runId, phase, error}. | | runner.plan-keep-failed | log | runner/runner.ts | Keeping a presented plan's text beside the run's outcome files failed, so the hook let ExitPlanMode through rather than hold a plan nobody could read. {runId, phase, error}. | | runner.relay-arming-failed | log | runner/runner-base.ts | Arming the relay for a session hit a failure: what: settings (rewriting the run's settings file with or without the PermissionRequest hook) or mcp-config (writing the phase's MCP document with the presence-only host — that session then runs on the floor). {what, error}. | | runner.preflight | log | runner/runner-control.ts | The run refused to begin — the tree, the docs root or the account check said no, and no signed-in account could be switched to. The run is parked with that reason and, when it is the auth wall, an errand naming what to sign in as. | | runner.qa-recover.unhandled | log | runner/runner-attempt.ts | A phase's QA recovery rejected past its own handlers. The phase is halted recovery-failed with the error in the reason, so a recovery that could not run is a recorded fact rather than a rejection on stderr nobody hears. | | runner.recover.crashed | log | runner/runner-control.ts | The recovery body threw while it was running — the recheck, the closeout or the operator's resume. The phase is halted recovery-failed, its lock and scope are released, and the stop this was meant to clear is still standing. | | runner.recover.refused | log | runner/runner-control.ts | The recover verb was refused on its own ledger (RCV-4): why: unchanged — the same evidence fingerprint as the last recovery of the phase — or capped. {runId, slug, phase, why, recovers}. | | runner.recover.unhandled | log | runner/runner-control.ts | The recovery's promise rejected past its own handlers, the lock-wait cap being the measured case. Same ending as a crash inside it: the phase is halted recovery-failed rather than the rejection going only to stderr. | | runner.reseat | log | runner/runner-loop.ts | Re-seating a squash-landed run branch on what landed threw. Nothing moved, the run carries on, and the next boundary asks again. {slug, error}. | | runner.widen-rule.failed | log | runner/runner.ts | The answer to a widen-rule card threw inside the runner's handler; the card stands answered, the rung open — the healer's next sweep settles it. {runId, phase, error}. | | runner.widen-rule.resume-failed | log | service-base.ts | A widen-rule card was approved after the loop that offered it had ended, and the recover verb that resumes the phase's own session refused or threw (a live runner, a claimed phase, a busy recovery). {slug, phase, error}. | | runner.release | log | runner/runner.ts | Releasing a phase's lock came back non-zero with output that does not read as already-free. The lock may still be held, which is what blocks the next boarding of that phase; detail carries the first 200 characters the script said. | | runner.lock-mirror | log | runner/runner.ts | A lock mirror threw before it could journal its phase.lock-mirror line — the script could not be started at all. Never fatal: the lock is on disk and the next mirror of that phase commits it. | | runner.retry-storm-frozen | log | runner/runner.ts | A lane retrying against a usage or overload wall would have been recycled or parked, and the fleet is held, so nothing was done. | | runner.settings | log | runner/runner-control.ts | Minting the run's token or writing its session settings file threw — at the run's first arming, or on the rewrite after a profile change. Children are launched with no settings path, so the deny list and the approval hook that file carries are not in force for them. | | runner.sigkill | log | runner/runner-control.ts | A child ignored SIGTERM through the grace and had to be killed outright, either while the whole run was stopping or on one phase's stop. Whatever it was in the middle of writing never got to finish. | | runner.silent-nudge-refused | log | runner/runner.ts | The first rung for a silent lane — one line into the open stdin — did not reach the child, which almost always means the child is already gone. No rung is counted and no ledger line written, so the recycle rung is still there once the lane settles. | | runner.silent-watchdog-frozen | log | runner/runner.ts | A silent lane would have been nudged, recycled or parked, and the fleet is held, so nothing was done: a held lane is quiet by construction, and acting on that silence would be the console mistaking its own hand for a fault. | | runner.tasks | log | runner/runner.ts | Reading what the session appended to its task list threw, so this drain folded nothing in and the phase's rows stay as they were. Worth strictly less than the run, so it never fails the phase. | | runner.thaw | log | runner/runner-control.ts | Waking a stopped child failed, so the freeze record is deliberately KEPT rather than cleared — clearing it would leave a SIGSTOPped process with nothing pointing at it. The verb answers false, and that pid may need a CONT signal by hand. | | runner.wall-reprobe-failed | log | runner/runner.ts | A walled phase's back-off re-probe could not read the account's meters (#78). The park stands on its clock and the next step of WALL_REPROBE_BACKOFF_MS tries again. | | runner.withdrew-queued | log | runner/runner-control.ts | A pause, halt or park cancelled the run's queue: entries scheduler admissions withdrawn and phases records put back to pending. Nothing already granted is touched, so a phase in flight keeps its scope until it ends. | | scheduler.agent-granted | log | runner/scheduler.ts | A ticket was admitted on its repository (control-tower phase 12, #29): {ticket, label, scope} — the grant with no run behind it (QUEUE_KINDS' agent). Every run lane of this console whose scope meets it queues behind it, named console/agent, until the ticket's terminal ends. | | scheduler.agent-refused | log | runner/scheduler.ts | A ticket was NOT admitted — a lock, a lane's grant or another console's grant holds a scope it meets: {ticket, label, scope, holders[{kind, owner, slug, phase}]}. Never queued: a person is at the button, and the route answers 409 naming the holder from its lock row. | | scheduler.agent-released | log | runner/scheduler.ts | A ticket's grant was dropped because its terminal ended — exited, closed or dismissed — or its mint failed: {ticket, label, heldMs}. What queued behind it is scanned again at once. | | scheduler.admitted | log | runner/scheduler.ts | A queued phase was granted its scope and may board; waitedMs is how long it sat in the queue behind other holders. | | scheduler.brake-released | log | runner/scheduler.ts | An account's usage brake was released (run.usage-brake-released is the run's journal line for the same act); what it held is scanned again at once. | | scheduler.braked | log | runner/scheduler.ts | An account's usage brake was engaged: pct the reading, until the window's reset (or null). While it holds, no NEW lane on the account is admitted while one is live on it — the only lane on an account is never refused. | | scheduler.rekeyed | log | runner/scheduler.ts | A run's waiting entries moved to another account (control-tower phase 78, #92) — runId, account, entries, and phase when one entry was asked for (a Retry). Every switch does it (a person's, the ladder's, onLimit's, the preflight's): each entry keeps its age and its place, and the scan runs at once, so a phase that waited only on the old account's window or brake boards in the same call. A live grant is not an entry and stays counted against the account it boarded on. | | scheduler.reservation-spent | log | runner/scheduler.ts | The lane kept for a phase boarded it (control-tower phase 100): the grant that admitted slug phase spent its reservation, and the scope and the slot it held are the queue's again. | | scheduler.bumped | log | runner/scheduler.ts | An operator moved one queued entry to the front of its priority class. One-shot and class-bound: the mark dies with the entry, so the same phase queueing again later starts at the tail. | | scheduler.machine-full | log | runner/scheduler.ts | A grantable lane was held back because the MACHINE is full (FLT-7): every lane fleet.json maxSessions allows is taken across the consoles of this machine, and {holders} names each one — instance, slug, phase. The entry waits behind a machine cap holder naming the other console, distinct from this console's own session cap, and is admitted when a lane anywhere is given back. | | scheduler.throttled | log | runner/scheduler.ts | A usage window was recorded for a whole account, so nothing paying with it is admitted until until passes; bucket names which window. Runs on other accounts keep going, which is the point of recording it per account. | | session.blocking-noticed | log | service-runs.ts | A terminal session was told, through its presence hook's answer (notice), that a queued phase waits on it (control-tower phase 82, #119): sessionId, runId, slug, phase, scope. Once per session per run. | | session.hold-released | log | service-runs.ts | An operator released a terminal session's hold on the queue (POST /api/sessions/<id>/release, control-tower phase 82, #119): sessionId, by, until (null — for as long as the session lives). The peer feed skips it from then on and the queue is polled at once. | | session.reap-failed | log | runner/reap.ts, runner/spawn.ts | The session reaper could not read the process table or end what it found (one-pool phase 26, #222) — error. Nothing is retried in a loop: the next sample or sweep asks again. | | session.reaped | log | runner/reap.ts | What a session left behind was ended with it (one-pool phase 26, #222): when (ended — at the session's close; sweep — the five-minute pass over sessions that already ended), the session's leader pid and sessionId, and per process its pids, commands and the ladder's endings. Reaped: whatever stayed in the session's own process group, and every DESCENDANT recorded while it lived (one ps every 30 s) that is still alive with the same start time, parented by pid 1, started before the session ended, and is a test runner or holds one below it. A session that declared waiting-external keeps its detached jobs for 12 hours (REAP_SPARE_MS) — the shape of a suite a phase runs detached and waits on. | | session.start | log | mcp/index.ts, accounts/index.ts | A claude start that serves no run opened, with the actor whole. Since control-tower phase 100 (#73) the MCP health probe writes mcp.probe.start instead, on a budget of its own; what is left here is a person's one-turn check of whether an account may run work (Settings ▸ Accounts, POST /api/accounts/:id/probe-entitlement), whose door is the press (door: operator, trigger: entitlement-probe) and which names the account and the owner the session declares (console/entitlement-probe). The journal twins are run.start (a run) and phase.session-start (a phase's reviewer, cloud review or pty agent). | | sessions.ask-suppressed | log | service-runs.ts | A session's wait was NOT announced as a session-ask, and reason says why: approval-card-pending — an autopilot lane whose approval card for the same plan and phase is already the ask (REG-5), with its approvalId; history — an ask the inbox held past the ten-minute horizon (REG-2), applied as a fact and remembered as said so a repeat of the same episode stays quiet, with its lateMs. sessionId, and slug/phase when the session is correlated. | | sessions.inbox-apply-failed | log | sessions/registry.ts | A presence drop from the session hook parsed but applying it threw. The file is deleted either way, so that one start, stop or end never reached the register and the session's state may be stale until its next hook event. | | sessions.inbox-deep | log | sessions/registry.ts | The session inbox held at least its depth watermark (200 drops) when a drain began (REG-2): depth, watermark, oldestAt and who drained (via: a console's inbox pass or the cli verb). Said once per crossing and re-armed when the inbox falls under half — a queue that deep means nothing drained it for a long time. | | sessions.inbox-drained | log | sessions/registry.ts, bin/sessions-verb.mjs | A drain applied presence drops (REG-2): via (inbox — a console; cli — phase-console sessions ingest, with no console up), applied, history (how many were older than the ten-minute horizon and applied as history — no push, no lock decision), refused and the worst lateMsMax. Every applied event also carries its lateness on the record's lastEvent. | | sessions.inbox-orphan | log | sessions/registry.ts | A *.json.tmp.<pid> drop whose pid is gone was reclaimed from the sessions inbox (REG-7): applied: true — a complete payload the hook was killed before renaming, applied as the event it was; applied: false — a torn one, removed. A tmp whose pid is live is a hook mid-write and is left alone. | | sessions.inbox-junk | log | sessions/registry.ts | A file in the hook's drop directory was not a presence payload, so it was deleted unread. One line per file — a flood of them means something else is writing into that directory. | | sessions.inbox-refused | log | sessions/registry.ts | A presence drop older than the age watermark (a week) was refused — not applied, removed (REG-2): sessionId, event, its own at, ageMs and via. A record it would create is one prune forgets on sight. | | sessions.notification-unmapped | log | sessions/registry.ts | A Notification hook event carried a notification_type this build maps to nothing — not a wait, not an answer, not a documented type it ignores (REG-8). The record is left exactly as it was; this line says the CLI's vocabulary moved. Once per distinct type per console, with the sessionId that first carried it. | | sessions.persist-failed | log | sessions/registry.ts | A session record could not be written to the state dir, so the register holds it in memory only and loses it at the next restart. Usually a full or read-only disk; file is the path it tried. | | sessions.poll-hook-failed | log | sessions/registry.ts | The console's end-of-poll hook — re-applying the presence moves its construction backlog deferred — threw. The poll itself completed; the deferral stays for the next poll. | | sessions.presence-apply-failed | log | service-base.ts | A presence change that arrived while the service was still being constructed threw when the backlog was finally applied. Each queued event is isolated on purpose, so the rest still landed and only this one session's move was lost. | | sessions.presence-backlog-full | log | service-base.ts | More than 500 presence changes queued before the service was ready, reported once at the flush BY KIND (SHD-7): dropped counts only the droppable moves — prune and heartbeat — evicted oldest-first to make room, and deferred counts real moves (a SessionEnd, a SessionStart, …) that could not be parked and wait, per session, for the registry's next poll (sessions.presence-reconciled). A real move is never dropped. | | sessions.presence-reconciled | log | service-base.ts | The registry's poll re-applied real presence moves the construction backlog could not hold (SHD-7): applied and the events, each against its session's record as it now stands — a deferred SessionEnd reaching the scheduler and the convergence loop one poll late instead of never. | | sessions.record-unreadable | log | sessions/registry.ts | A session record file would not parse and was deleted, so the register has forgotten that session. Heartbeats never conjure a record back — only a fresh hook event will, so a live session reappears at its next turn and a dead one is simply gone. | | sessions.watch-failed | log | sessions/registry.ts | The watcher on the hook's drop directory could not be armed, so drops are only picked up by the 30 s poll. Degraded to latency, never to silence. | | sessions.watcher-error | log | sessions/registry.ts | The watcher on the drop directory errored and was closed. The poll re-arms it on its next pass, so the same degradation applies — presence moves land within 30 s instead of immediately. | | sessions.events-capped | log | sessions/registry.ts | A session's raw event log (sessions/<id>.events.ndjson) reached its 1 MB cap and STOPPED. One marker line is written into the file itself, so a reader can tell "this session was quiet" from "we stopped listening" — the distinction a silently truncated file destroys. Written once per session, never per event. |

| sessions.suspect | log | service-base.ts | A session the registry calls live, whose process ps says is scheduled, has written nothing to its own events file for twenty minutes — not even the heartbeat that lands every minute while it lives. A SUSPICION and never an action: the session is very often somebody's own CLI sitting at a prompt in another window, so this console records what it can see and reaches into nothing. Said once per silence, and cleared the moment the session writes anything. Pro. |

| sessions.events-failed | log | sessions/registry.ts | A session's raw event log could not be appended to — a read-only state directory, most often. The session RECORD is unaffected: presence keeps working and only the sequence beside it is lost, which is the right way round for the two. | | shell.command | log | shell.ts | One non-git, non-engine child process the console ran, through the command seam (5.1.0): a worktree's sh -c setup command, du, gh (issues, watch refs), tailscale, a credential probe, the editor opener. Same fields and same level rule as git.command; PHASE_CONSOLE_DEBUG=shell. streamed: true marks a command the seam did not run itself — §Verification streams its output to a live reader, so it owns its own spawn and there is no tail to collect. | | shutdown.announced | log | service-base.ts | The drain waited for the shutdown announcement's delivery (SHD-4): the notification id, whether a delivery was in flight (waitedFor), whether the bounded wait ran out (timedOut) and the delivery outcomes the record left with — never empty: the reports that landed, or the console's own skipped row saying why none did. | | shutdown.begin | log | index.ts | The drain has started: {reason} is what asked for it, intent WHY (shutdown, restart, or signal for a stop no request explains — SHD-8), via how it ends (exit, launchctl, systemctl, signal), mode for a Shut down press, signal when a signal carried it, and {successor} the command of a self-restart when one is planned. A "stay off" press's SIGTERM reads reason: SIGTERM with intent: shutdown — never indistinguishable from a logout again. | | shutdown.bootout | log | lifecycle.ts | The stop order was handed to the supervisor — {label} with {args}, detached so it outlives this process. Expect the console to be gone moments later and to stay gone until the unit is started again. | | shutdown.bootout.failed | log | lifecycle.ts | Handing the stop order to the supervisor threw, so the job was not unloaded. The caller falls back to an ordinary drain and exit — which under a supervisor that keeps the job alive means the console comes back rather than staying stopped. | | shutdown.disabled | log | lifecycle.ts | A "stay off" Shut down carried out its unload (SHD-5): the unit label, whether every disable step succeeded (disabled), whether the final stop was handed to the supervisor (spawned) and how many steps the plan had. disabled: false leaves the stop marker as the only thing holding the next boot. | | shutdown.end | log | index.ts | Every registered handler has drained and the process is about to exit 0 — the last line a deliberate stop writes. {reason} and intent match the ones that opened the drain. | | shutdown.failed | log | lifecycle.ts | The registered stop handler threw when the Shut-down verb called it, so nothing is draining and the caller was told the request failed. The console is still running; stop it by hand. | | shutdown.handler | log | lifecycle.ts | One registered drain handler finished cleanly — {name} is which and {ms} how long it took. They all run in parallel, each against its own deadline. | | shutdown.handler.failed | log | lifecycle.ts | One drain handler threw or blew its deadline; {name} is which and {ms} how long it had. The exit carries on regardless, so whatever that handler was going to checkpoint was not checkpointed. | | shutdown.inventory | log | service-base.ts | What the exit abandons, written before anything closes on every path (SHD-1, SHD-8): the drain's intent, the lanes, the count of clocks and the soonest, the runs, the live sessions, the pending approvals and the unread inbox depth. Each abandoned run also gets its own run.console-shutdown. | | shutdown.inventory-failed | log | service-base.ts | Reading what the console is holding threw. The readiness answer degrades to an empty inventory marked empty: false, so a bare press still has to acknowledge; at the drain, no run.console-shutdown rows are written. | | shutdown.journal-failed | log | index.ts, service-base.ts | Writing an abandoned run's run.console-shutdown (or the whole pass) threw; the exit carries on. | | shutdown.marker-clear-failed | log | lifecycle.ts | Removing the stop marker threw. The caller re-reads the file and refuses the release if it is still there. | | shutdown.marker-unreadable | log | lifecycle.ts | The stop marker did not parse, so it reads as no marker — a console that wrongly believes itself stopped does nothing, silently, which is the worse failure. | | shutdown.marker-write-failed | log | service-base.ts | A "stay off" Shut down could not write its stop marker, so nothing was stopped: without the marker the unload would be the old bootout a login undoes. The press is refused with the error. | | shutdown.refused | log | service-base.ts | A Shut down press was refused (SHD-1, SHD-2): the derived actor, the mode asked for, what it needs (acknowledge — a non-empty inventory, or any unload; unload — no unit to unload; mode — not a strength), the reason sentence the API answered with, and the inventory in digest. | | shutdown.requested | log | index.ts, service-base.ts | Someone asked this console to stop and the press went through: the derived actor (by, via, origin, remoteUser — see restart.requested), supervisor, stop (how it is carried out: launchctl, systemctl or exit), mode (exit or unload), durability (what the plan achieves: returns, until-login, stays-off, disabled — SHD-5), acknowledged, sessions and busy, and the inventory it acknowledged, in digest (SHD-1). Written before anything closes, because afterwards there is nothing here to write it. index.ts writes it with via: signal and the signal's name as origin when the stop arrived as SIGINT/SIGTERM that no API request explains — by is then unknown, because the sender of a signal is nobody the console can name. | | sighup.ignored | log | index.ts | The controlling terminal was closed and the console deliberately stayed up, so a run in progress is not killed by closing a window. Ctrl-C, the Shut-down button, or the supervisor's own verb remain the ways to stop it. | | skills.scan | log | skills.ts | Reading the skills and slash commands a phase could invoke threw partway through the project, personal and plugin sweep. Whatever had been collected is still returned and cached, so the offered list may be short rather than empty. | | sizing.census-failed | log | service-base.ts | The sizing census — this console's own phase.session and phase.tokens lines read into the measured session caps, the context model and the sessions per phase (control-tower phase 59, #83) — threw; the previous census, or the shipped table, keeps answering and the next read retries within the hour. | | spawn.bad-effort | log | runner/spawn.ts | The requested reasoning effort is not a value the CLI accepts, so the flag was dropped and the session runs at the CLI's own default. allowed lists the valid ones — fix the plan or the run setting, because the CLI would only have warned and carried on at the wrong effort. | | spawn.bypass-downgraded | log | runner/spawn.ts | The CLI refused the bypass permission mode and silently fell back to default, which in headless mode refuses every edit — so the run looks right and can change nothing. The disclaimer must be accepted once, interactively, in a normal session on this machine; until then use the Trusted profile. | | spawn.bypass-permitted | log | runner/spawn.ts | The argv guard let --permission-mode bypassPermissions through because the operator chose the bypass profile for this run. Loud on purpose: this is the line that hands a session the keys. | | spawn.idle-close | log | runner/spawn.ts | The session finished its phase turn and then went totally silent for afterMs with stdin still open, so the console closed stdin for it and let it end. reason says whether operator messages were left unechoed or the stream simply stopped. | | spawn.folded-unechoed | log | runner/spawn.ts | A turn's result reported queued_turn_count: 0, so the operator messages (marks) written at least FOLDED_MARK_GRACE_MS before it were read, folded into the turn without the echo the close rule waits on; they leave unecho, and stdin closes at the turn's end rather than at the idle closer ten minutes later (one-pool phase 24, #259). | | spawn.init-idle | log | runner/spawn.ts | The init→result bound fired: the session emitted its init, then went afterMs without a productive event and without its first result, so the spawn ended it itself (endedBy: spawn-watchdog) through the ordinary interrupt-first teardown. | | spawn.line-overflow | log | runner/spawn.ts | One line of the CLI's output grew past 8 MB with no newline, so the buffer was dropped rather than left to eat memory. Whatever was in it is gone; the stream carries on from the next newline. | | spawn.no-first-event | log | runner/spawn.ts | The first-event backstop fired: the child produced nothing productive inside its window, so it was torn down through the ordinary abort path and settles as a normal session exit. reason separates no output at all from nothing but API retries, and from output this build could not parse. | | spawn.parse-error | log | runner/spawn.ts | A line of the CLI's stream was not JSON this build can read, logged with a 200-character sample. Only the first three are logged in full and the rest are counted, because a CLI writing malformed output writes a lot of it. | | spawn.parse-errors | log | runner/spawn.ts | The tally of unreadable stream lines, written once as the session settles. A non-zero count means the console's reading of that session is incomplete — its cost, turns and result text may all be short. | | spawn.refused-flag | log | runner/spawn.ts | A flag that would take the repository's guard rails off was stripped from the argv before the child started. Unconditional — no profile or setting unlocks these — and droppedValue appears when the flag's value had to go with it so it could not become a loose positional prompt. | | spawn.refused-value | log | runner/spawn.ts | A forbidden value on an otherwise legitimate flag was rewritten, not dropped; replacedWith is what the child actually got. Removing the flag entirely would fall back to the interactive default, which refuses every edit in headless mode. | | spawn.stderr | log | runner/spawn.ts | The child's stderr pipe raised an error, so anything it writes after this is not kept or classified. The session itself carries on — only the evidence for reading its ending is thinner. | | spawn.stdin | log | runner/spawn.ts | The child's stdin broke with something other than the routine EPIPE of a session that has already ended, and the pipe is now closed. Nothing more can be written into that session: no steer, no nudge, no answer to a question it asked. | | spawn.stdin-write | log | runner/spawn.ts | Writing into the session's stdin threw, so the steer or nudge never reached it. The caller is told the write failed and records that instead of a line claiming something that did not happen. | | spawn.stdout | log | runner/spawn.ts | The child's stdout pipe raised an error, so the stream the console reads the whole session from is broken. Events after this are lost and the attempt settles on what it had already seen. | | sse.slow-client | log | api/sse.ts | A live-updates client stopped reading and passed its buffered-bytes cap, so it was retired and its socket let go. Nothing is lost: a browser reconnects with Last-Event-ID and the service replays what it missed — which is exactly what a held-open socket full of stale frames was preventing. stream, bufferedBytes, cap. | | sse.socket | log | api/routes.ts | A live-updates response socket raised an error that is not an ordinary client disconnect, and that subscriber was retired. One browser stopped receiving events and reconnects on its own. | | sse.write | log | api/sse.ts | Writing a chunk to an event stream failed for a reason other than the client going away, so that subscriber was dropped. stream names which one — events or the debug tail. Every other browser's stream is untouched. | | start | log | index.ts | The console process came up and wrote its first line — {pid, node, port, allowWrites}, plus previousRunCrashed when a console before it left its per-boot marker behind (a hard end — see previous-run-crashed). Logging is configured before anything else can fail, so this is the top of every boot. | | start-ceiling.refused | log | service-base.ts | The start ceiling refused an automatic start and there was no run to write run.start-refused on (or there was — the log line is written either way): the actor with its door, the ceiling, limit, count and until. Announced on health once per window. | | static.error | log | http/static.ts | A file being streamed to a browser failed mid-transfer and the response was destroyed. Client disconnects are filtered out before this line, so it is a real read error — usually the file at {path} was deleted or replaced under a live console. | | static.missing | log | http/static.ts | A file the caller had already stat'ed was gone by the time it was opened, so the request got a plain 404 rather than crashing the server. Almost always a rebuild swapping the client output out from under a live request. | | terminal.closed | log | terminal.ts | An operator killed a live session: the pty was signalled, every attached browser was sent an exit and the record dropped. Deliberate, never a fault. | | terminal.dismissed | log | terminal.ts | An already-exited session record was cleared away by hand, so its scrollback and its resume id are gone for good. | | terminal.evicted | log | terminal.ts | The oldest dead session records were dropped to stay under the ceiling; kept is how many exited ones are retained. A claude --resume of an evicted one is no longer offered. | | terminal.freeze-escalated | log | terminal.ts | A frozen session was never thawed and its escalation clock came due, so the console wakes the process and ends it politely. by names whoever froze it and afterMs how long it was left. | | terminal.frozen | log | terminal.ts | A session's process group was stopped at an operator's request, with escalateAt recording when it will be ended if nobody comes back for it. The promise that a freeze is not forever is that clock. | | terminal.listener | log | terminal.ts | A lifecycle listener threw while being told a session was created, changed or ended. The session and its pty are unaffected, but some part of the UI may not have heard about that transition. | | terminal.no-broker | log | terminal.ts | The pty broker could not be reached or would not start, so terminals are reported unavailable for this console. error means the connect threw, socket means it answered nothing — the board and everything else keeps working. | | terminal.no-pty | log | terminal.ts | The broker answered but has no working node-pty, so no shell can be opened at all. reason is the broker's own explanation, usually a native module that was never built — run npm install in the viewer directory. | | terminal.no-ws | log | terminal.ts | The ws module could not be loaded, so no browser can attach to a terminal even where the pty side is healthy. Everything that is not a terminal keeps working. | | terminal.opened | log | terminal.ts | A pty was started — which shell, in which directory, under which pid, and whether the broker owns it rather than this process. This is the line that ties a session id to a real process. | | terminal.resize | log | terminal.ts | A resize was sent to a pty that has already gone. Harmless and deliberately not fatal: the session record outlives its process, and a dead one rejects the call. | | terminal.resume-failed | log | terminal.ts | Asking the broker at start-up which sessions it still holds failed, so nothing was adopted. Any sessions the broker is keeping stay alive but do not appear on this console until it is reachable again. | | terminal.resumed | log | terminal.ts | Sessions the broker held across a console restart were adopted back and the sweeper restarted; adopted is how many. This is what makes a restart cost no live shell. | | terminal.retired | log | terminal.ts | The sweeper dropped a session record whose retention window closed with no browser attached. Expected housekeeping, not a fault. | | terminal.socket | log | terminal.ts | A browser's WebSocket to a session raised an error. The wire is removed when it closes; the pty, its scrollback and every other viewer are untouched. | | terminal.spawn-failed | log | terminal.ts | A pty could not be started at all and the caller was answered 500 with a hint. For a claude session the overwhelmingly likely cause is the claude CLI not being on this console's PATH. | | terminal.stop-escalated | log | terminal.ts | A session asked to stop was still alive when its grace period elapsed, so it was SIGKILLed. graceMs is how long it was given to go quietly. | | terminal.stopping | log | terminal.ts | SIGTERM was sent to a session and the grace clock started; a frozen one is woken with SIGCONT first, because a stopped process never runs its handler. by names who asked. | | terminal.thawed | log | terminal.ts | A frozen session was continued and its escalation clock cancelled, so the process is running again and nothing will end it on a timer. | | terminal.upgrade-refused | log | terminal.ts | A WebSocket upgrade on the terminal path was turned away — disabled means no terminal capability is on, bad-token an expired or forged ticket. The ticket itself is deliberately never written to the log. | | transcript.append | log | runner/transcript.ts | One line of a phase's replay could not be written. The live view is unaffected — it streams from the child — but the replay a later reader opens will be missing that event. | | transcript.full | log | runner/transcript.ts | A phase's replay file hit its size cap and is closed to further writes; one last line goes in past the gate that just shut, to say why the replay ends there. The run and the live stream carry on. | | trigger.fire-failed | log | triggers.ts | A stored trigger's firing threw before its answer could be written (control-tower phase 98, #137): id, error. The trigger itself is already spent on disk — the press is never repeated — and its firings row says what became of it. | | ultrareview.on-demand | log | service-runs.ts | An operator asked for a cloud review over a run's last finished phase, outside the at-settle path. state: landed carries a verdict and a finding count; unknown means the review could not answer, and the reason travels with the result. | | upgrade.error | log | index.ts | A socket errored during a WebSocket handshake and it was not the ordinary case of a client going away. Caught here because an upgrade socket has no response object to hang errors on, and unhandled it would be an uncaught exception per request. | | upgrade.unhandled | log | index.ts | The terminal socket handler rejected while answering a handshake, so the socket was destroyed with no reply. Logged at error, not warning: terminals cannot connect until whatever threw is fixed. | | verify.path-amended | log | runner/verify.ts | A §Verification command was about to run with standard directories missing from PATH, so they were appended; added lists them. Said once per process, and a sign the console was launched from something without a login shell's environment. | | verify.queued | log | runner/runner-attempt.ts | The log twin of phase.verify-queued (one-pool phase 26, #327): a heavy line waiting for the machine's heavy-job budget — slug, phase, purpose, command, reason, running, budget. | | verify.stragglers | log | runner/verify.ts | A §Verification (or Setup:) command's group leader exited while members of its process group still held its output (control-tower phase 106, #168): after a short drain grace the survivors were named and stopped through the signal ladder, and the line settled on the LEADER's exit code — command (condensed) and pids. The row says the same in its output (left N process(es) in its group … — stopped) and carries them as stragglers. A sweep that passed and left Metro holding its stdout used to hold the phase in verifying for 50+ minutes. | | verify.terminal | log | service-runs.ts | An operator re-ran one of a phase's recorded §Verification commands in the integrated terminal, in the phase's own directory. Only commands the run itself recorded can be started this way, and the session's exit is reflected back onto the phase record. | | watch.landed-failed | log | watch-scheduler.ts | A watched ref landed and the healer that should have acted on it threw, so the landing is treated as deferred rather than delivered: {ref} keeps its due time and the next pass re-delivers it instead of charging a delivery that never happened. | | watch.runs-failed | log | watch-scheduler.ts | Listing the runs to scan threw at the top of a pass, so the pass ran against an empty list and no parked phase was probed or resumed. The clock re-arms and the next pass tries again. | | watch.run-failed | log | watch-scheduler.ts | One run's part of a pass threw — a record the rotation could not read — so {slug}'s refs were not asked this pass. Every other run's were: each run is its own try, and the clock re-arms whatever happened (one-pool phase 23). | | watch.pass-failed | log | watch-scheduler.ts | A whole pass threw outside any one run. The timer is re-armed in the pass's finally, so the clock goes on; before one-pool phase 23 a throw here left it unarmed for as long as the process lived (#337). | | watch.landed-slow | log | watch-scheduler.ts | The healer did not answer one landing within {boundMs} (WATCH_LANDING_BOUND_MS), so the pass moved on and read it as deferred: nothing is charged, the drive it may have started holds the re-offer through resumeInFlight, and the next pass asks again. | | watcher.deaf | log | watch.ts | The docs tree changed but no filesystem event arrived, so the watch is delivering nothing: every watcher is closed, rebuilt, and the tree re-read from disk. The console is marked unhealthy until the rebuild takes. | | watcher.error | log | watch.ts | One directory's filesystem watcher failed and was dropped rather than left in place lying about being healthy; failures is the running count. A rebuild is scheduled with backoff and the console is unhealthy meanwhile. | | watcher.handler | log | watch.ts | The change handler itself threw while re-reading the tree, on a debounced batch or on a full flush. Those changes never reached the board — the next event or heartbeat is what recovers it. | | watcher.rearmed | log | watch.ts | Watchers were rebuilt after a failure and the tree re-read, so anything that changed while the console was blind still lands. afterMs is the backoff that was waited, and it keeps doubling while the set is still short. | | webhook.failed | log | webhooks.ts | A delivery to somebody's chat endpoint did not come back 2xx. The hook's failure count goes up and it is quiet for a backoff window; status: 0 means the request got no answer at all. {id, origin, status, failures}. | | webhook.persist-failed | log | webhooks.ts | The hooks file could not be written, so the latest change to the list — a new hook, a failure count, a backoff window — is in memory only and gone at the next boot. | | wide-bind | log | index.ts | The console bound to {host} — an address reachable from the network rather than loopback. Said once on stderr at boot and once here so the fact is findable in the record later: anyone who can reach the port can drive this console. |

Fleet supervisor log events

The fleet supervisor (viewer/fleet/, the com.phase-console.fleet unit) writes the same log shape to a file of its own, <stateHome>/fleet/fleet.log; the two console-side lines about it (fleet.machine-released and fleet.release-resume-failed) land in each console's own log. Same columns and the same two-way rule as the tables above: viewer/test/docs-parity.test.ts walks viewer/fleet/ beside viewer/server/.

eventsinkemitted bymeaning
fleet.access-refusedlogfleet/index.tsThe supervisor turned a caller away before any route. reason is the rule — assertion-sent (the caller sent the header only the supervisor sets), no-reach (a proxied identity arrived before fleet.json names a remote host and its logins), or the console's own unknown-host, no-identity, not-allowed and proxied-as-local — with the host and the path; upgrade: true marks a socket handshake.
fleet.announcelogfleet/fanin.tsOne console's announcement went to the fleet subscription: console names who spoke, with the category, how many devices were tried and how many were sent. The per-device outcomes went back to that console, which wrote them onto the notification's own delivery record.
fleet.announce-failedlogfleet/link.tsA console could not fan an announcement in (port, error): the supervisor did not answer, or refused. The console's own devices were still told, and it does not ask again until the supervisor beats again.
fleet.autostartlogfleet/index.tsSaid once at the supervisor's boot (FLT-9): the registered consoles with a unit that a login starts, the ones it holds (autostart: false, or a stay-off marker), and the machine ceiling maxSessions their lanes share.
fleet.bearer-createdlogfleet/index.tsThe supervisor minted its bearer, <stateHome>/fleet/fleet.token (0600), on its first boot. Every console verifies the supervisor's assertion against that file and derives its own token from it, so it is minted once and never rotated by itself.
fleet.device-enrolledlogfleet/reach-devices.tsA device was paired at the direct door and given its cookie: the id it will be known by, via the pairing code or the password, and standalone when it was added to the home screen. The secret itself existed for one response and is kept only as a sha256 — nothing here or anywhere else can hand it back.
fleet.device-logoutlogfleet/index.tsA paired device logged itself out at the direct door (id): its register row is revoked and its cookie cleared in the same answer. Only a device can do this, for itself — the machine revokes a device with POST /api/fleet/reach/devices/<id>/revoke, which logs fleet.device-revoked.
fleet.device-refusedlogfleet/reach-devices.tsA pairing was refused before it began because the register is full (reason: 'full', with the live devices count). Revoke a device the household no longer uses; the cap is a person's number, not a fleet's.
fleet.device-reissuedlogfleet/reach-devices.tsA device that has been carrying the same secret for 30 days was given a fresh one (id) and swapped its cookie in place. The old value stops working at once, which is what bounds what one stolen cookie is worth.
fleet.device-renamedlogfleet/reach-devices.tsA device's label changed (id), and its device:<slug>#<id6> login moved with it — so a console's own audit line names the phone the operator recognises.
fleet.device-revokedlogfleet/reach-devices.tsA device was revoked (id). Its cookie is refused from the next request, its slot is free for a replacement, and its row stays as the record that it was once here.
fleet.faultlogfleet/index.tsAn uncaught exception or rejection in the supervisor, recorded and survived: it is every phone's one door, and one bad request must not close it.
fleet.holdlogfleet/lifecycle.tsThe machine hold went down (by, at). Every console of the machine now refuses every automatic start at fire time; no live session was stopped — freeze on a console is what stops work where it stands.
fleet.inbox-console-failedlogfleet/index.tsThe fleet inbox asked a console for its own inbox and it did not answer (console, error) — refused, timed out, or not listening. Its rows are missing from the merge and it stands as a no-process health row instead; said once per console per beat, however often the page asks.
fleet.joinlogfleet/join.tsA console's start registered through the supervisor: its id, the port the election left it, whether it is the default, and how many presence drops recorded before any console claimed its directory were adopted into its inbox.
fleet.machine-releasedlogservice-runs.tsThis console noticed the machine hold lift (heldBy, heldAt) and resumed what the hold deferred — the queue, the boot re-adoption, the healer — as a thaw does. Not written while this console's own freeze still stands.
fleet.pair-burntlogfleet/reach-devices.tsSomebody guessed wrong five times, so every outstanding pairing code was burnt. A person who cannot type and an attacker who is guessing look the same from here, and the safe reading of either is that the code on the operator's screen is no longer private — mint another.
fleet.pair-expiredlogfleet/reach-devices.tsA pairing code was offered after its ten minutes were up. Nothing was let in and nothing was burnt; the code is simply gone.
fleet.pair-mintedlogfleet/reach-devices.tsA pairing code was minted and shown to the operator once (expiresAt). The code is kept as a sha256, so it can never be shown again — a lost screen means a new code, not a recovered one.
fleet.pair-oklogfleet/direct.tsA device paired at the direct door and was handed its __Host-phase-console cookie: its id, via the pairing code or the password, and standalone when it paired from a Home Screen app. The register logs fleet.device-enrolled beside it; this line is the door's half — the request that earned it.
fleet.pair-refusedlogfleet/direct.tsA pairing attempt at the direct door was refused (reason: code-unknown, code-expired, code-burnt, password-wrong or password-unset; via). Every refusal is counted against the caller's address by the limiter, so the eleventh in ten minutes is refused before it is tried (fleet.reach-limited).
fleet.password-clearedlogfleet/reach-devices.tsThe direct door's password was removed. Pairing by code still works; nothing already paired was affected.
fleet.password-setlogfleet/reach-devices.tsA direct-door password was set, hashed with scrypt at the model's parameters. The plaintext never leaves the request, and the parameters are stored beside the hash so they can be raised later without locking anyone out.
fleet.ledger-fold-failedlogfleet/poller.tsThe supervisor could not fold the consoles' usage ledgers into fleet/usage-ledger.json (the error; one-pool phase 6); the file keeps its last fold and the next beat tries again.
fleet.poller-claimedlogfleet/poller.tsThe supervisor took the machine's one usage poller (pid). The learned store's pollerOwner names it, and every console follows its reads instead of polling.
fleet.usage-refreshlogfleet/index.tsA following console's Refresh asked the supervisor to read (control-tower phase 76, #110): the console that asked, the credential fingerprint when it named one, and how many accounts were read. The console adopts that read once the answer lands, instead of re-adopting the share it was pressed to replace.
fleet.poller-heldlogfleet/poller.tsAnother live claim already holds the machine's poller (pid, since), so this supervisor polls nothing: two claims would read every account twice.
fleet.poller-lostlogfleet/poller.tsThe supervisor's claim was gone when it went to beat — another claim took over a beat it missed — so it stood down rather than fight. Consoles follow whichever claim is live, or poll for themselves.
fleet.poller-releasedlogfleet/poller.tsThe supervisor gave the poller back on a clean stop; each console polls for itself again at its next tick.
fleet.proxy-failedlogfleet/proxy.tsA console behind /c/<id>/ did not answer the hop (id, the path, error; upgrade: true for a socket). Its heartbeat still says running, so it is starting or wedged — its census row's lastSeenAt says which.
fleet.reach-bind-retrylogfleet/direct.tsThe direct door could not bind (state: address-unassigned when the machine does not hold address right now — a laptop off its network — or port-in-use when something else holds port; code is the system's word) and will try again every retryMs. Logged once per kind for a door's life, however many retries follow: the state and lastError in /api/fleet/health and daemon.json are the live reading.
fleet.reach-ca-mintedlogfleet/tls.tsThe direct door's local CA was minted (fingerprint256). This is the root an operator installs on their phone and reads back against what the console shows, so the fingerprint is deliberately sayable — it is the one derived value here that is public by design.
fleet.reach-cert-reloadedlogfleet/tls.tsAn operator-supplied certificate pair changed on disk and was picked up by the poll (source: 'custom'). The listener was never closed: the new context goes in through Server.setSecureContext.
fleet.reach-changedlogfleet/index.tsThe supervisor applied a changed reach from the machine profile — at boot when it asks for a direct door, then on every edit its five-second poll or POST /api/fleet/reach finds — with the mode, host and port it now stands on. The door was opened, moved (closed, then opened on the new block) or closed without the supervisor restarting.
fleet.reach-leaf-mintedlogfleet/tls.tsA leaf was signed for the door's address (sans, notAfter). IP:127.0.0.1 is always among the SANs, so the supervisor, the CLI and doctor reach the door over loopback without an exception to the rule.
fleet.reach-leaf-renewedlogfleet/tls.tsThe leaf was re-minted, with a NEW key, and why: missing (there was none, or it did not parse), expiring (inside the 30-day window) or sans-changed (the profile now names a different address). The CA is never re-minted by this path — rotating the root a phone trusts is fleet reach ca rotate, a deliberate act.
fleet.reach-limitedlogfleet/reach-devices.tsThe door refused an attempt before trying it, with the scope that refused: source (ten failures from one address in ten minutes) or global (fifty from anywhere). The global half exists because a botnet gives every attempt its own address; the per-source half because one wrong phone must not lock the household out.
fleet.reach-openssl-missinglogfleet/tls.tsNo openssl could be found to sign with, and the door reads tls-unavailable. tried is the search, in order: PHASE_CONSOLE_OPENSSL (which, when set, is used or nothing — it never falls through), then each PATH entry, then /usr/bin. Node mints the keys either way; only the signature needs the binary.
fleet.reach-refusedlogfleet/direct.tsThe direct door answered 421 to a request whose Host is not one it admits (reason: 'reach-host', the host it sent) — a scanner, a stale bookmark, or a name the profile does not carry. Said once per host per ten minutes, so a sweep is a line rather than a log.
fleet.reach-server-errorlogfleet/direct.tsThe direct door's HTTPS server emitted an error after it bound (error) and kept serving. A bind that fails is fleet.reach-bind-retry instead, and never reaches this line.
fleet.reach-statelogfleet/direct.tsThe direct door moved between two of its states (from, to), with the address, port and exposure it stands on and, when it is not listening, the reason in words. listening and closed are info; every other state is a warning, because each is a phone that cannot reach the fleet.
fleet.reach-tls-failedlogfleet/tls.tsA certificate could not be minted, read or paired (reason) — a failed openssl run, a PEM that does not parse, or an operator's certificate and key that do not belong together. Always a reading the door can report, never a throw: a supervisor that died to announce a certificate problem would be worse than no door.
fleet.relay-failedlogfleet/relay.tsA console's #/fleet page asked the supervisor through that console's relay and the supervisor did not answer (path, method, error: refused or timed out). The page was answered 502 with the reason; nothing was retried.
fleet.releaselogfleet/lifecycle.tsThe machine hold was lifted (by, and heldBy since heldAt). Consoles admit again at their next fire-time read and resume what they deferred.
fleet.release-resume-failedlogservice-base.tsA console noticed the machine hold lift and the resume it hands to threw (error). Automatic starts still admit at fire time; the queue and the boot re-adoption wait for the next scan, a thaw or a boot.
fleet.request-failedlogfleet/index.ts, fleet/direct.tsA request threw inside the supervisor (url path, error) and was answered 500, or its half-sent response was dropped. door: 'direct' marks one that came in through the direct door.
fleet.server-errorlogfleet/index.tsThe supervisor's HTTP server emitted an error after it bound (error) and kept serving. A bind that fails is a refusal at start instead, and never reaches this line.
fleet.sighup-ignoredlogfleet/index.tsThe supervisor's terminal went away and it kept running; a stop it means is SIGTERM or SIGINT.
fleet.spawn-failedlogfleet/lifecycle.tsStarting a console with no unit installed could not spawn the bin (file, error), so a start the supervisor answered 202 did not happen — run phase-console start in that project.
fleet.startlogfleet/index.tsThe supervisor is up: its pid, the loopback port, the remote hosts it answers to, and whether it holds the poller.
fleet.stoplogfleet/index.tsThe supervisor stopped cleanly and wrote stoppedAt into daemon.json, so every console stops fanning in at once instead of after three missed beats.
fleet.verblogfleet/lifecycle.tsA verb ran on one console through the supervisor: id, verb, by, the HTTP status answered and whether it was ok. The console's own agent or API keeps its own record of the act.

Restart-update log events

A restart brings the console back on the latest version of its copy (2026-09-18): the service runs deploy/self-update.sh through server/self-update.ts before the process exits, and these lines say what it did and whether the restart followed — and, for a restart pressed mid-run, how it waited for the live sessions to finish first. They land in the console's own log beside restart.requested, with the same columns and the same two-way rule as the tables above.

eventsinkemitted bymeaning
restart.cancelledlogservice-base.tsA restart that was waiting for the live sessions was called off (POST /api/restart {cancel: true}, the card's Cancel). The restart hold lifts at once and the scheduler and converge are nudged, so the held phases board. Carries the actor.
restart.drainedlogservice-base.tsThe live sessions a waiting restart was holding out for have finished — no lane is live and no card is unanswered — so the update and the restart go ahead now, through the drain: the runs checkpoint and carry on from the new process. waitedMs is how long the wait took.
restart.drain-cappedlogservice-base.tsA waiting restart reached its cap — max(2 h, expected end + 30 min) from when it began — with lanes still live, and goes ahead anyway (control-tower phase 48, #70): the lanes are checkpointed on the way out and adopted by the next console, and a verification it cuts is re-run (phase.reverify-after-restart). Carries the actor, waitedMs, capAt and the lanes and approvals still live.
restart.expected-endlogservice-base.tsWhen a waiting restart expects to be done, from the lanes it waits for — a session's size estimate less what has elapsed, a verification's timeout, a few minutes for a lane settling — written when the wait begins and again whenever the lane set changes, with the fixed capAt and each lane (slug, runId, phase, stage, estimateMs) (control-tower phase 48, #70).
restart.unit-refusedloglifecycle.ts, service-base.tsA restart would have brought this console back on node arguments other than the machine profile's (nodeArgsFor, control-tower phase 48, #71), and its launchd unit cannot be re-rendered — its ProgramArguments are not the installer's shape, or the file cannot be rewritten. The restart is refused naming the re-install (command: agent.sh install with every argument the console runs with); at says whether the press was refused at the door, before a wait or an update was spent, or at the exit. A forced one (forced: true) restarts as it is. Carries the actor or the label and why.
restart.unit-reloadloglifecycle.tsA restart re-rendered this console's launchd unit in place (rerendered), or found it right on disk while this process runs an older loaded definition, and started launchd's reload — a detached sh that boots the job out (its SIGTERM is this console's ordinary drain, recorded as the restart with via: reload), waits until launchd has let go, and bootstraps the file (#71). Carries the label, plist, what this process running, what the unit gave node, what it gives now (to), the reload's pid, and the log it writes.
restart.unit-reload-failedloglifecycle.tsThe unit reload could not be started (error). The restart goes ahead through the definition launchd holds; the file is right now, so the console's next restart finds this process's arguments behind it and reloads then. Same fields as restart.unit-reload.
restart.update-failedlogservice-base.tsA restart's update did not take — the build or the install failed at the new commit — and deploy/self-update.sh put the copy back where it was, so the restart goes ahead onto the version the console was already running. Carries the actor and {result, from, to, target, npmCi, warnings, reason}; target is the commit it could not build.
restart.update-heldlogservice-base.tsA restart's update answered and the restart did NOT follow: the copy could not be put back (broken), another update held it (busy), a run started while it built, or no restarter was registered. The process keeps serving from memory and the detail says which; the restart hold lifts and the scheduler and converge are nudged. Same fields as restart.updated, plus detail.
restart.update-startedlogservice-base.tsA restart began by updating the copy (2026-09-18): the press was answered at once, deploy/self-update.sh runs, and every automatic start waits on the restart hold until it answers. Carries the actor and force.
restart.updatedlogservice-base.tsA restart's update answered with a copy that is safe to run — updated (fast-forwarded and built), built (rebuilt where it stood), current, packaged or skipped — and the restart follows. {result, from, to, target, npmCi, warnings, reason} beside the actor; warnings says why a copy was not moved (local edits, commits of its own, no upstream, an unreachable remote).
restart.waitinglogservice-base.tsRestart was pressed mid-run with whenIdle: rather than cut a session in half, the console holds new phases of the plans its live lanes touch (the restart hold) and waits for those lanes to finish, then updates and restarts. Carries the actor, whether it will update, the lanes and approvals still live at the press, its expectedEnd and capAt, the plans it holds (only those whose scope meets the lanes' — control-tower phase 48, #70; null when it holds every plan) and, when a run session asked, that session.

License log events

The Pro package's license (control-tower phase 67) writes these to the console's own log, never to a run's journal: a license belongs to the machine, not to a run. server/license/log.ts writes each one when the console notices a move — at boot, at a re-check, or at the first read after phase-console license … changed the file, since the CLI answers on its own terminal and writes no log. A source checkout writes license.source at boot and nothing else. Every line but the boot pair carries the state, why, the mode (package or test) and whichever of keyId, licenseId, recheckAt, graceEndsAt, expiresAt and fallback the license has, plus from (the state it left) or at: 'boot'.

eventsinkemitted bymeaning
license.activatedloglicense/log.tsThe license opened: a key was activated on this machine, a re-check brought a grace, expired, revoked or invalid license back, or the console booted holding a checked one (at: 'boot'). Pro surfaces answer from here on.
license.check-failedloglicense/log.tsA re-check could not be answered — the license server was unreachable, rate-limited it, did not know the license, or sent a file this console would not accept (reason). Nothing on disk changed; the offline grace is still counting, and the clock asks again in an hour.
license.deactivatedloglicense/log.tsThe license closed to unlicensed: phase-console license deactivate freed this machine, or a re-check learned the account page had freed it (the site's 402 unlicensed). The file is gone and the gated surfaces answer 402.
license.expiredloglicense/log.tsThe license closed to expired: the offline grace ran out with no answer from the license server (why: grace-ended), or the term ended and this build was released after updatesUntil (why: term-ended). The license errand says which, and the gated surfaces answer 402.
license.gate-missingloglicense/log.tsAt boot, a package that carries no gated lists — a bundle built without the lists compiled in, or an unbundled tree with neither the manifest nor gated.json — so its gate refuses nothing. A package build defect; the console keeps running.
license.graceloglicense/log.tsA check fell due and the license server has not answered since: the license is inside its offline grace (graceEndsAt), still open, and the clock keeps asking.
license.invalidloglicense/log.tsThe license closed to invalid: the file does not verify (a bad signature, an unknown kid, a malformed file, another machine's fingerprint — why), or this machine's clock reads earlier than the high-water mark (why: clock) and will until it advances.
license.refreshedloglicense/log.tsA re-check came back with a fresh signed file, stored in place of the old one: the license is active and its next check is recheckAt.
license.revokedloglicense/log.tsThe license closed to revoked: a re-check was answered 402 revoked (a refund, a dispute, the admin's revoke). It locks at once; activating another key opens it again.
license.sourceloglicense/log.tsAt boot, this console runs from a source checkout — free/manifest.json and scripts/build-free-tree.mjs are both present — so the license reads source, every verb works and no key is asked for. Carries the license file's path, which a source checkout never reads.