You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The CI reading says when a pipeline is still running or was canceled (#856): checkCiStatus() gains an optional phase (running | canceled); status keeps its three values green / red / unknown, so a caller that never reads phase sees no change. phase: 'canceled' is set only when no failed pipeline sits behind the canceled one and none was set aside for the same commit; otherwise the reading stays the red alert it was. The dispatcher no longer cuts a candidate's readiness to a quarter for either phase, the autonomy gate G3 reads both as "no verdict" with their own warning, and the session-start banner prints CI running on HEAD … — no verdict yet or last CI run on HEAD was canceled … — no verdict instead of "could not be determined" or 🚨 CI RED. scripts/release.mjs still accepts only green. Replayed over the last 100 real pipelines: no status changed, the two canceled ones carry phase.
Fixed
so-hud no longer reports a failed question as the owner's refusal (#1856): the protection gate wrapped $.ui.ask in .catch(() => null) for both outward-action steps and the collision question. A question that could not be shown therefore looked like a closed dialog, the model was told "Der Owner hat … abgelehnt … nicht erneut versuchen", and aktionen.jsonl recorded abgelehnt although the owner never decided.
The gate still refuses in that case, but now says the question could not be shown and that the owner did not decide, and records ergebnis: "fehler" (already allowed by the receipt schema).
The engine rejects $.ui.ask with the same HooksError when the owner answers without a label: …: no answer (the dialog was dismissed) for a closed dialog, …: no answer (The user doesn't want to proceed …) for Esc (claude-code 2.1.312). Both stay a refusal; any other or unknown wording counts as a failed question, so a changed engine message can never invent an owner decision.
Codex hooks start on native Windows (#1849): every handler in hooks/hooks-codex.json started through SO_PLATFORM=codex … sh hooks/run-node.sh …. PowerShell reads SO_PLATFORM=codex as a command name and cmd.exe has no env prefix, so on native Windows no hook ran, the guards included (reproduced 2026-10-28: Windows 11, PowerShell 5.1.26100, exit 1).
Each handler now also carries commandWindows: set NoDefaultCurrentDirectoryInExePath=1&& node -e "require(require('path').join(require('path').isAbsolute(process.env.PLUGIN_ROOT||'')?process.env.PLUGIN_ROOT:process.exit(1),'hooks','codex-win.cjs'))" <handler>.mjs. Codex runs Windows hooks through %COMSPEC% /C (cmd.exe; codex-rs hooks/src/engine/command_runner.rs). The set stops cmd.exe from running a node planted in the session directory before the one on PATH; node reads PLUGIN_ROOT itself and uses it only when it is absolute, so a plugin root with spaces needs no quoting and a relative one cannot load a launcher from the session directory's node_modules. The new hooks/codex-win.cjs sets SO_PLATFORM=codex and CODEX_PLUGIN_ROOT and runs the handler with stdin/stdout/stderr and exit code passed through. The banner prints through node -e console.log because cmd.exe pipes on |.
Measured natively (PowerShell 5.1 and cmd.exe, Node 24.14.0, root with spaces): the destructive guard denies printf x > CLAUDE.md and allows ls -la; an empty or relative PLUGIN_ROOT exits 1 without loading anything. A real Codex run on Windows needs the owner's hook trust and is not measured yet.
The Codex plugin contract requires commandWindows on every handler, in exactly this form and for the same handler as command (windows-entry, trusted-windows-wrapper, windows-entry-mismatch). The changed hook file needs a new trust approval in Codex.
An abandoned stub now says whether it did any work (#1835): a backfilled abandoned record carried total_waves / total_agents / total_files_changed as placeholder zeros, so a count of status: "abandoned" mixed empty restarts with sessions that ran agents. On one fleet ledger, two of six stubs since 2026-10-25 carried 15 and 86 agent.stopped events.
The backfill now writes an optional work_evidence field: { platform, turn_stops, agent_stops, wave_completions, activity_events }, event counts from the session's own events. Turn stops are counted under the current name and under the pre-2026-09-06 name orchestrator.session.stopped, and the larger of the two counts is used. Every event beyond start/end bookkeeping counts as activity, and so does every Skill call in skill-invocations.jsonl.
The field is written only when the session's history was read whole, it started fresh (native_sourcestartup/clear, not compact/resume), it has exactly one raw id, and its platform emits turn stops (claude, codex). Otherwise it is absent, never 0.
A null line in events.jsonl no longer turns a backfill into an error.
classifyStubWork() sorts a stub into empty / conversation / dispatched / unmeasured.
The new read-only scripts/session-ledger-census.mjs classifies old stubs by joining the events ledger. It prints close and abort rates with and without measured empty restarts, and names every population. --stubs lists each stub with its class. status stays abandoned.
A child-process timeout under load no longer blocks the push (#1673): the full gate re-runs failing test files serially when the parallel run failed only by timeouts, but it recognised only vitest's own Test timed out in Nms. A spawnSync(…, {timeout}) or execFileSync(…, {timeout}) that runs out reaches vitest as Error: spawnSync <cmd> ETIMEDOUT (measured, vitest 4.1.12), so the whole run counted as a real failure. On 2026-10-28 a push whose only red tests were one vitest timeout and one spawnSync /bin/zsh ETIMEDOUT was blocked for that reason. At base 888da931, 143 test files set a timeout: option next to such a call (upper bound, rg).
The classifier now accepts that error line too. Everything else stays fail-closed: an assertion that mentions ETIMEDOUT, other errnos, Command failed: …, a Serialized Error or Caused by: line, and any mixed run. The serial re-run still has to pass, so a real hang stays red.
A red test step that gets no re-run now says why: orchestrator.quality_gate.* carries rerun_skipped (e.g. non-timeout-failure, too-many-files, gate-timeout), from the CLI and from the remote pre-push route. None of the 16 quality_gate.failed events since 2026-10-18 carried a re-run or a reason.
tests/so-hud/hud.test.mjs accepts discovery-timeout beside discovery-fallback: under load the 700 ms git worktree list ceiling fires, and that assertion was the one red test the re-run may never rescue.
Leftover process groups of the gate itself and of its re-run reach the event (#1708): leftover_groups now also lists the gate sub-script's own reaped group (command: "gate", or the gate command in the library emitter) and the serial re-run's (command: "test-rerun"). The re-run's timed_out, kill_signals and survivors join the event's kill fields.
The security delta reads pnpm 12 lockfiles again (#1837): with a packageManager field, pnpm 12 writes the package manager's own dependencies as a first YAML document ahead of the project lockfile. yaml.parse refused two documents (MULTIPLE_DOCS), so the whole file counted unparsed and none of its new packages reached OSV. readLockfileDelta now reads every document and merges their packages; a document with errors still counts as unparsed.
A session without a navigator boundary now gets the Standard-Grenzsatz (#1842): fleet-auflagen.mjs --render printed nothing for an absent or rejected conditions file, or an accepted file without grenze, so a session could search across repos with no boundary rule in its prompt.
scripts/lib/fleet-protocol.mjs exports STANDARD_GRENZSATZ; renderAuflagenNotice() is never empty. An accepted navigator grenze is still printed verbatim. For absent, rejected, an accepted file without grenze, or a withheld grenze (control characters, not a string, empty or blank; the withheld line comes first), it prints the Standard-Grenzsatz under the header fleet-auflagen: grenze (Standard-Grenzsatz: no navigator boundary accepted, binding):.
The boundary binds immediately and fail-closed; unlike the other Standard-Auflagen it does not wait for fallback_due_at. Documented in skills/_shared/fleet-protocol.md § Standard-Auflagen, skills/session-start/SKILL.md Phase 7.6 and the operations contract.
A fleet check-in no longer fails when another repo holds the semantic session id (#1839): the semantic id collides across repos, and fleet-checkin.mjs then exited 2 without a trace. The navigator was also observed writing a session's conditions under the native session UUID while the session had checked in under the semantic id.
The check-in carries an optional session_uuid; the CLI stamps it from this process's native id when that is a native UUID (readProcessLocalSessionIds(): CLAUDE_CODE_SESSION_ID or CODEX_THREAD_ID by platform, none when an inherited id contradicts the other; a process-local witness that overrides input). When another repo's check-in holds checkin/<semantic id>.json, the file is written once more under checkin/<session_uuid>.json; session stays the semantic id. stdout gains key and key_fallback.
Every refusal emits the new orchestrator.fleet.checkin_rejected (reasonoccupied|changed, fallbackuuid|none), also when the UUID retry then fails with a write error (exit 1). Exit 2 now means nothing was written.
fleet-auflagen.mjs <session_id> [<session_id>...] [--render] tries the same process-local native id first, then each positional id; among accepted files the newest zeit wins (a resumed session keeps its UUID, so an older UUID-keyed file must not shadow a fresh semantic one; a zeit outside the UTC-seconds form or more than 5 minutes ahead ranks last), else the first rejected, else absent. The result carries key; the repo check per key is unchanged.
The dialectic deriver no longer aborts when the input is slightly over budget (#1840): on 2026-10-28 this repo's input estimated 34,392 tokens against the default dialectic.budget-tokens of 32,000, so every run ended budget-exceeded before the agent.
runDialecticDeriver() now drops the lowest-ranked learnings as whole records until the prompt fits, tells the model the actual top_n_learnings, and reports learnings_used / learnings_dropped in the result. No text inside a record is cut.
budget-exceeded is returned only when even zero learnings do not fit (the fixed load of peer cards, steering and sessions); used is then the full-input estimate, as before.
Session start reads the canonical VCS when origin is only a mirror (#1846): in a clone with a GitHub snapshot mirror on origin and the canonical GitLab remote beside it, detectVcsFamily() reported ambiguous: true and both of its callers took the mirror. The mode selector's backlog scan read 7 GitHub issues instead of 148 GitLab issues, the one priority::critical among them included, and the CI banner would have read the mirror's checks. The Session Config vcs: value now breaks an AMBIGUOUS tie (detectVcsFamily({ preferVcs }), new exports normalizeVcsPreference and readConfiguredVcs; only the leading token counts, so vcs: github — prose works). Unambiguous remotes keep their answer and an explicitly passed vcs still wins. Measured over 145 repos on one host: 19 ambiguous, 4 change family, each to its configured value.
npm run update:local fetches and fast-forwards from main's configured upstream (branch.main.remote / branch.main.merge) instead of a fixed origin/main, and falls back to origin/main only without an upstream. updateRepo is exported with a ctx.root and tested against real git repositories.
A skill call is no longer counted twice when the skill ledger is tracked (#1841): backfill and census added up the skill-invocations.jsonl rows of the main checkout and of the worktree, so a row committed into both counted twice in work_evidence.activity_events. countSkillInvocationsAcrossFiles takes the highest count per row identity (timestamp, skill, session, event) across the files instead of the sum; distinct rows still add up.
The fleet check-in audit keys duplicates on session and repo (#1843): the same semantic session id in two repos no longer marks both records ambiguous; two records with the same id in one repo are still duplicate-session.
The worktree-removal guard measures worktrees whose path differs from git's only in letter case (#1829). On macOS git reports the spelling on disk (_wt/FeedFoundryV2/…) while the guard compared it as a string with the spelling git lists (_wt/feedfoundryv2/…), so every such removal was denied as "not measurable". The guard now compares the directory itself (device and inode), and a denial says which step failed.