@lyupro/codex-bridge 0.6.1 → 0.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,592 +1,716 @@
1
- # Changelog
2
-
3
- All notable changes to this project will be documented in this file.
4
-
5
- The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
-
7
- ## [0.6.1] - 2026-09-07
8
-
9
- ### Fixed
10
-
11
- - `update` names the package it acted from. Standing in a clone while an older copy answers from
12
- PATH, the command compared the home against its own files and said `codex-bridge is up to date` —
13
- true about the package that ran, silent about the one on screen, and the cause had to be found by
14
- hand. Every outcome now carries the name, version and path of the copy that answered, and when
15
- that copy is not the checkout the operator is standing in, a second line names both versions and
16
- the command that changes which one answers.
17
- - `permissions add` reports the state of the rule set instead of the size of its own change. A
18
- complete set answered `Added 0 permission rule strings.`, where zero reads as a refusal rather
19
- than as "all of them are already there".
20
-
21
- ## [0.6.0] - 2026-09-07
22
-
23
- ### Added
24
-
25
- - `codex-bridge model` answers which model and reasoning depth each of the three roles runs on, and
26
- where each value comes from — the operator's config file or the package default. The config is one
27
- file per machine, shared by every project on it, and the command says so: changing a model here
28
- changes the executor for every repository this host delegates from.
29
- - `codex-bridge model list` prints the live catalogue, hidden models included and marked as hidden.
30
- A model Codex does not advertise is still a model the operator may choose; omitting it would mean
31
- deciding for them and then explaining why the system has a model the list denies.
32
- - `codex-bridge model set <role> <model> [effort]` and `model unset <role>` write that choice. The
33
- pair is checked against the catalogue entry of that very model at the moment of writing, so
34
- `ultra` — supported by the newest model and rejected by the package's own flat list until now —
35
- became reachable, and a depth the chosen model does not offer is refused with its supported
36
- depths named. Nothing is substituted silently: the command never changes what it was not asked to.
37
- - `codex-bridge model speed <role> <tier>` pins a paid acceleration tier, and `model speed <role>
38
- unset` removes it. The tier is spelled with the identifier the catalogue reports for that model,
39
- never a word held in the package, and the command writes nothing until a second call ends in
40
- `confirm` — the first prints the catalogue's own description of the tier and a link to the page
41
- carrying the credit multipliers. The installer now also places one `ask` rule of its own, so the
42
- host asks the operator before an agent can run that command at all.
43
- - A pinned tier reaches the run: the launcher passes `-c service_tier=<id>` when, and only when, a
44
- role has one pinned.
45
-
46
- ### Changed
47
-
48
- - The depth of a run is no longer validated against a list held in the package. That list had
49
- drifted from the server in both directions — it offered a depth no model accepts and refused one
50
- the newest model supports — so the writing command asks the catalogue and the hot path checks only
51
- the shape of the value. The arbiter at run time is Codex itself, whose refusal is more accurate
52
- than a copy of its data.
53
- - One boundary now owns every write to the run config, and it persists only the edit it was given.
54
- Reading merges the package defaults, and the previous writer saved that merged object, so a single
55
- edit would have frozen every default into the operator's file — worst of all the list of
56
- environment paths, which the package extends as it learns of new ones and which decides whether a
57
- change belongs to the run or to the environment.
58
- - The word `mode` no longer names three different things in this package. The reviewer's flag is
59
- `--changeset`, the prune says which strategy, the table which truncation, and one registry answers
60
- who the three agents are.
61
- - The README states the suite size it actually has.
62
-
63
- ### Fixed
64
-
65
- - Two edits of the config racing each other can no longer lose one of them. Both commands read the
66
- role profiles, waited seconds for the catalogue, then wrote what they had read, and three
67
- concurrent processes all reported success while one edit was missing from the file. The edit
68
- boundary now reads the file itself, hands the fresh value to the caller's transformer, compares
69
- the bytes immediately before publishing, and holds the lock this package already used for the
70
- rules-ownership registry — one definition now serves both writers.
71
- - The worktree lock reads the shell rather than the prose someone wrote into a document. A plain
72
- `cat > file << EOF` handed the whole command, heredoc body included, to the analysis, so the word
73
- `touch` inside an English sentence made the next words look like files being written. The reverse
74
- hole closed with it: the tail after a heredoc's closing marker was not parsed at all.
75
- - A dead run stays dead even when Windows hands its process id to someone else.
76
- - The reader of the Codex user configuration answers about configuration, not about prose inside it.
77
- A multi-line TOML literal could contain a line indistinguishable from an assignment, and it was
78
- read as the setting; an empty argument threw past the module's own error boundary, which exists so
79
- the table renders whatever state the file is in.
80
-
81
- ## [0.5.5] - 2026-08-28
82
-
83
- ### Fixed
84
-
85
- - The write guard now reads quoting, so a redirect character inside a quoted argument is text rather
86
- than an operator. While a delegated run held the repository, `sed -E 's#(A ).*#<redacted>#'` was
87
- refused as a write to a file named `#'`: the scan ran over raw text, so the `>` looked like a
88
- redirect and the token after it looked like its destination. The file already carried a defence
89
- against this class, added after a `node -e` expression was misread the same way, but that defence
90
- filtered candidates holding characters no file name may contain and the one refused here held
91
- none. The guard still fails closed: when quotes cannot be balanced the scan behaves exactly as
92
- before, because answering "no write here" to a line it cannot parse would be worse than the false
93
- refusal being removed.
94
- - The installation summary no longer reads as a bug. Four `PreToolUse` guards printed as
95
- `PreToolUse and PreToolUse and PreToolUse and PreToolUse` because the sentence joined every hook's
96
- event; it now groups by event and counts.
97
- - The installed-file count agrees with `doctor`. The summary counted the Codex rules file among the
98
- files while `doctor` reports it on its own line, so `68` sat next to `67` and read as a lost file.
99
- - `Granted 0 permission rule(s)` no longer reports the good outcome as if it were a failure. When
100
- the set is complete the summary says so; on a host whose settings were edited by hand it says how
101
- many rules are missing and how to restore them, instead of claiming all are in place while the
102
- operator gets permission windows. The already-installed branch reports the set by reading it —
103
- granting there would have restored a deleted rule under the heading "nothing to do".
104
- - A contract probe that refuses to judge now carries the host's own words. `The host exited with
105
- status 1.` was the entire account of a failure on a live host; the last non-empty line the host
106
- printed is now part of the reason. What counts as honoured, ignored or inconclusive is unchanged.
107
-
108
- ## [0.5.4] - 2026-08-28
109
-
110
- ### Added
111
-
112
- - A gate that holds every registered hook to one rule: a hook may not add anything to what the host
113
- runs. A neighbouring package spent a whole plan undoing the opposite habit — its `PreToolUse` hook
114
- wrapped the agent's command in a pipe, and because a host permission rule is matched against the
115
- beginning of the final string and is not applied at all once that string carries a substitution or
116
- a compound operator, every command it touched became unresolvable by the operator's own rules.
117
- This package reported those windows on 2026-08-15 while carrying none of the cause. The gate reads
118
- both behaviour and sources: a hook may return only the keys that refuse or speak
119
- (`permissionDecision` of `deny` or `ask`, `permissionDecisionReason`, `additionalContext`), and no
120
- hook source may mention a key that replaces what the host is about to run. `permissionDecision:
121
- "allow"` is refused as well — the host stopped honouring it between 2.1.119 and 2.1.231, and while
122
- it worked it was a blanket auto-approve over the operator's own deny rules.
123
-
124
- - `doctor` and `install` now say whether this host still honours a hook refusal. Every guard in this
125
- package refuses work with `permissionDecision: "deny"`, and the sibling key of that same object
126
- died quietly — a neighbouring package established that `"allow"` stopped being honoured somewhere
127
- between host 2.1.119 and 2.1.231, with no error printed on the way. Nothing here would have
128
- noticed `deny` going the same way, because the suite checks that a hook RETURNED a refusal, never
129
- that the host APPLIED it. The record is bound to a host version, since that is what changes
130
- underneath an installation, and reports five states: never probed, probed on another version,
131
- refusals ignored (which names the guards it makes inert and exits non-zero), verified, and a host
132
- whose version cannot be read.
133
-
134
- - `codex-bridge doctor --probe-contract` performs that measurement on a live host, and every message
135
- that a probe could change now names it. The probe builds a throwaway rig outside the repository,
136
- registers one temporary hook there that refuses one marker command, runs the real host inside it
137
- and judges by whether the marker file appeared. Three conditions were bought by a measurement that
138
- first reported a working host as dead: only the tool under test is allowed (`--allowedTools Bash`),
139
- because the nested session otherwise sidesteps the refusal with `Write` and writes its own report
140
- of being blocked into the marker file; the verdict comes from what executed, never from what the
141
- model said about it; and settings are read from the project alone, or the operator's own hooks get
142
- measured instead. A crash, a timeout or a hook that never fired records nothing and exits 2 — an
143
- absent marker is only evidence when the host survived to the end, and a probe that announced a
144
- contract as verified without measuring it would be the very silence this record exists to break.
145
-
146
- ### Fixed
147
-
148
- - A run now reads the operator's configuration instead of the copy shipped inside the package. The
149
- installer seeds `config.json` into `~/.lyupro/.codex-bridge/`, the CLI read it from there, and the
150
- runner built its own path from the module it happened to live in — so `codex-bridge run` opened the
151
- package's seed, in which no profile is ever configured. A model and a reasoning depth pinned by the
152
- operator on 5 August reached no run at all: every run silently used defaults, `env.json` recorded
153
- `"models": {}`, `meta.json` recorded no model because no `-m` was ever passed, and `doctor` kept
154
- confirming the setting because it resolved the path correctly. Three releases shipped that way, and
155
- it surfaced only when a service refused a reasoning level and the command line in the run folder
156
- turned out to name no model. Installed from npm, the file the runner read also sat inside
157
- `node_modules` and was replaced by every update, contradicting the documented promise that
158
- configured profiles survive one. There is now one resolver of the host home for both halves of the
159
- repository, the printed state names the file together with where that path came from, and an
160
- end-to-end test fails if a configured profile stops reaching the assembled Codex command line — the
161
- existing unit tests injected the profile as an argument and could not see the file at all.
162
-
163
- - Every reply now names the worker that ran it: `Model: <model> at <depth> effort (<where the depth
164
- came from>)`, beside the log link. `worker.json` and `meta.json` carry the same profile, including
165
- whether the model came from the configuration or was left to Codex, and whether the depth came
166
- from the request, the configuration or the fallback. A dispatcher reads these rows and nothing
167
- else, and for three releases none of them named a model at all — which is why a run on a model
168
- nobody ordered could go unnoticed. A depth silently served by the fallback is the same silence one
169
- level down, so it is reported the same way. Runs recorded before the field simply keep their old
170
- replies.
171
-
172
- - `minimal` is no longer accepted as a reasoning effort. It passed validation and was then rejected
173
- by the model itself (`Unsupported value: 'minimal' is not supported with the … model`), so an order
174
- died four seconds after launch on quota already spent. The allowed set is now exactly what the
175
- service enumerates: `none`, `low`, `medium`, `high`, `xhigh`, `max`. A validator that admits a
176
- known-dead value is worse than no validator: it moves the refusal past the point where refusing is
177
- free.
178
-
179
- ## [0.5.3] - 2026-08-23
180
-
181
- ### Fixed
182
-
183
- - The worktree lock knew writing by tool name, and a shell writes anyway. It denies edits inside a
184
- repository held by a live `codex-build` run, but was registered only for
185
- `Write|Edit|MultiEdit|NotebookEdit`; on 2026-08-16 a `python - <<PY` heredoc inside `Bash` walked
186
- past it and appended to `CHANGELOG.md`, and the run reported `FAIL — out-of-scope changes (1)`
187
- for work it had finished correctly. Two guards now cover the gap from both sides. The lock's
188
- matcher takes in shell tools as well, and `shell-write-intent.mjs` refuses the obvious forms —
189
- redirection, `tee`, `sed -i`, `cp`, `mv`, `rm`, `touch`, `truncate`, a heredoc fed to an
190
- interpreter — before they run. Behind it, `worktree-witness.mjs` compares the tree after every
191
- shell command against the run's recorded snapshot, drops everything inside the run's declared
192
- scope, and names whatever is left: a run may only write inside its scope, so the rest is another
193
- hand. The recogniser states in its own header that its list is not complete and cannot become one
194
- — that is precisely why the witness stands behind it.
195
-
196
- - The recogniser knew `sed` and did not know `sed.exe`. Its interpreter pattern had always allowed
197
- the suffix — `python.exe` opens a heredoc like `python` does — but the command list compared bare
198
- basenames, so the spelling this platform hands over walked past the guard while the identical
199
- command without the suffix was refused. Found accepting a delegated run of 2026-08-17 that had
200
- recorded the gap as intended behaviour: a test asserting `sed.exe -i` is not a write. A test that
201
- states a defect is true keeps it, so the recogniser now strips the suffix and the case asserts the
202
- refusal.
203
-
204
- - Delegated read-only runs could not execute a single command. codex-cli 0.149.0 on Windows builds
205
- its sandbox from `windows.sandbox` — `elevated` or `unelevated` — and the unelevated token refuses
206
- CreateProcess outright: every command a run tried came back as
207
- `Rejected("… rejected: blocked by policy")` before the process existed. The setting lives in the
208
- operator's `config.toml`, which `--ignore-user-config` drops on purpose, so on 2026-08-23 every
209
- `codex-scout` and `codex-review` run reported honestly that it could not read anything and was
210
- then failed by its own quality gate for having no code references. `codex-build` kept working, and
211
- that looked like "read-only is broken" — but it is the one agent that does not pass
212
- `--ignore-user-config`, so it inherited the key from a line in a personal file the package does
213
- not own; with that flag its `workspace-write` runs are refused just as flatly. Four probes on the
214
- installed CLI, differing by this flag alone, separate the two. All three agents now pass
215
- `-c windows.sandbox=elevated` on Windows, so isolation from the operator's config stays intact and
216
- the sandbox each agent is entitled to is the one it actually gets. The value carries no quotes
217
- because the command line goes through cmd.exe, where a double quote is refused before launch.
218
-
219
- - A hook fix could not reach a host that already had the hook. Installation identifies our entry by
220
- its command and ignores which matcher group it sits under — deliberately, because the matcher
221
- grows whenever a host spelling is added and a lookup by matcher would register a duplicate and
222
- orphan the old entry at uninstall — but nothing afterwards moved the entry to the matcher the
223
- package declares now. A host installed before 2026-08-17 kept `Write|Edit|MultiEdit|NotebookEdit`
224
- for the worktree lock while the package had been saying `…|Bash|PowerShell` for days:
225
- `update --force` reported success and rewrote nothing, so the guard never saw a shell command and
226
- two honest delegated runs were failed for a file written through their window. Merge now moves the
227
- entry under the declared matcher, drops the group it emptied, leaves a group holding someone
228
- else's hooks exactly as it was, and still cannot produce a second registration of our command for
229
- one event.
230
-
231
- - `doctor` stated the matcher this package expects as though it had read it. Its hook line was built
232
- from the registry, so a host still registered under an older matcher was reported as healthy with
233
- the new matcher's text — on 2026-08-17 it printed `…|Bash|PowerShell` over a `settings.json` that
234
- said `Write|Edit|MultiEdit|NotebookEdit`, and the operator had no way to see that the worktree
235
- lock could not see a shell command at all. Same shape as the 2026-08-10 defect in this file:
236
- a check that compares the package with itself cannot fail. The line now prints the matcher the
237
- host recorded, and a difference from the declared one is a `warn` naming both values and
238
- `codex-bridge update --force`.
239
-
240
- - The worktree lock refused four kinds of work it cannot protect. A miss by this guard is
241
- survivable — the witness names the fact afterwards — but a false refusal has no backstop and
242
- simply stops the orchestrator. An ignored path was refused, which made the plans and checklists a
243
- live run is documented in unwritable, although a run is only ever failed for what
244
- `git status` reports; an unexpanded shell variable was resolved against the working directory and
245
- refused as a path inside the repository; a backtick in the TEXT of a heredoc was refused as a
246
- path; and a comparison operator in code (`node -e "i>0?'yes':'no'"`) was read as a redirection.
247
- The lock now passes a path git proves is ignored — and only that, so a missing repository or a
248
- missing git keeps the refusal, while `.git` stays refused whatever git answers — a redirect
249
- target wins over document text (the 2026-08-16 python heredoc is still refused), and a candidate
250
- opening with `$`, `%` or a backtick, or holding a character no file name may contain, is not
251
- reported as a target.
252
-
253
- - Every number this package advertises about itself was wrong. The npm description said "5 guards,
254
- 604 tests" — a count three releases old, next to a registry that had grown a sixth hook the same
255
- week — README said 705 while the suite ran 757, and CLAUDE.md said five guard hooks. Each was
256
- caught by eye during a release, which is how the one in `package.json` survived three of them: a
257
- number written in prose has no reader that can fail. The whole-suite run now compares the size
258
- README claims against what the run reported and exits non-zero naming both, and a test holds the
259
- guard count in README, CLAUDE.md and the package description against `HOOK_DEFINITIONS`. Deleting
260
- the sentence fails the same way an outdated one does; a single-file run reports nothing to
261
- compare and stays silent.
262
-
263
- ## [0.5.2] - 2026-08-16
264
-
265
- ### Fixed
266
-
267
- - The guard punished the very refusal the package demands. A dispatcher whose command the host
268
- denies is told by its own prompt to answer `FAIL`, name its order id and name `codex-bridge
269
- install`; the guard knew only `RUN=` and `ATTACH=`, so that honest answer read as "no run
270
- exists" and was blocked three times before the exhausted try budget let it through. A probe on
271
- 2026-08-16 with `Bash(codex-bridge:*)` denied spent 31.5k tokens on an answer worth ten, and the
272
- counter recorded `{"state": 3}` against it. A host refusal is now a recognised shape beside the
273
- two run lines: a reply that declares failure, names its order id and names the remedy passes on
274
- the first try, before any check that looks for a folder on disk. A reply that declares failure and
275
- names the remedy but omits the order id is blocked with that exact part named — the prompt asked
276
- for it in prose, and prose is a request a model can silently skip.
277
-
278
- - No Claude Code host had registered the three dispatchers since 2026-08-10. The stop-guidance
279
- sentence added that day — `` before `TaskStop`: TaskStop removes the wrapper … `` — was
280
- substituted into the `description:` line of each agent's frontmatter, and an unquoted YAML scalar
281
- cannot contain a colon followed by a space: the frontmatter did not parse, so the agents did not
282
- exist as far as the host was concerned. `doctor` reported every file present the whole time,
283
- because it counted files instead of reading them. Installation now re-emits frontmatter as valid
284
- YAML — any value a reader would not take as a plain scalar is quoted, the sentence survives
285
- verbatim — `doctor` parses each installed definition and checks its `name`, and the suite parses
286
- everything the installer would write.
287
-
288
- - A run ordered under another run's order id is no longer answered with that run's verdict. On
289
- 2026-08-15 a build ordered as `plan42-run3` carried the id of `plan42-run2`, and the runner
290
- printed run2's `OK`, run2's file list and run2's suite over a worktree where nothing had been
291
- done — only a manual `git status` stopped a commit of work that did not exist. An order id names
292
- one task: when the id already belongs to a run whose task text differs, the runner now refuses
293
- with exit code `2` before printing anything, naming the folder that owns the id and the two
294
- remedies (a new `--order-id`, or `--continue`). Repeats of the same task, continuations, and runs
295
- recorded before the `task_hash` field attach exactly as before.
296
- - The same collision is now refused one step earlier and checked one step later. `order-gate` denies
297
- the dispatcher call itself when the ordered id already belongs to a run with a different task —
298
- before any quota is spent — and `reply-guard` blocks a reply whose run records a different
299
- `order_id` than the one the orchestrator ordered, read from the dispatcher's own transcript. Both
300
- read the single definition of "the same task" the runner uses; a second copy of that rule would
301
- drift silently, which is the failure this change exists to end. Every uncertain disk state — an
302
- unreadable task file, a missing runs directory, a run folder written before its `status.json` —
303
- passes, because a guard nobody can predict is a guard everyone works around.
304
- - Delegating to a dispatcher no longer stops on a permission prompt. The shipped agent definitions
305
- started a run by absolute path — `node "<installed dir>/run-codex.mjs" --agent … \ …` — and a host
306
- matches permission rules against the beginning of a command line, so a rule written for this
307
- package could never match it. The only way to start a run is now the package command,
308
- `codex-bridge run`, on a single line: no interpreter, no path, no line continuation. The task
309
- statement travels as `--task-file`, because a heredoc forces the invocation back into the
310
- multi-line form that no rule can cover; stdin remains supported for the direct call, and giving
311
- both channels is a refusal rather than a silent precedence.
312
- - Installed hooks could not run at all. The installer reshaped the source tree — hooks to one
313
- directory, their modules to another — so every hook's relative import was correct in the clone and
314
- false on the host, and all five guards died on `ERR_MODULE_NOT_FOUND` from the day the package
315
- moved to its branded folder. The suite stayed green because it exercises the clone, and `doctor`
316
- called a hook healthy on the strength of a line in `settings.json` without ever starting the file.
317
- The source now contains a literal image of the installed home, `src/home/`, and installation
318
- copies it one to one.
319
- - The reply guard no longer sends a refused dispatcher down the one road the host cannot allow.
320
- Seven of its block texts said "Run run-codex.mjs and return its stdout verbatim" — the sentence
321
- removed from every prompt in this release, because a command line naming a file can match no
322
- permission rule. They were the worst place for it to survive: a dispatcher reads them at the one
323
- moment it is looking for what to do next, having just had its answer rejected. All seven name
324
- `codex-bridge run`. The invariant behind them was the actual defect and is now stated as a rule
325
- rather than as a shape: no line of code in a hook may name the runner file, whole-line comments
326
- excepted. The earlier expression-bound check could not see the text a session is shown when its
327
- turn is ended, which is where the same sentence would have survived a fourth cleanup.
328
- - `doctor` no longer calls a host healthy when an installed agent definition is not this package's.
329
- Drift was advisory — a message, exit code 0 — while an unreadable definition was already fatal. A
330
- checklist run planted the pre-0.5.2 invocation, a file path instead of the package command, in an
331
- installed agent: `doctor` printed the drift and still exited 0, so a script or a CI step would
332
- have called that host healthy while every delegation on it stopped at a permission prompt. Any
333
- definition that differs from the package now exits 1 and names `codex-bridge update --force`.
334
-
335
- ### Added
336
-
337
- - `codex-bridge run` — the public entry point for starting a delegated run, with the same flags,
338
- stdout and exit codes as the historical file invocation.
339
- - Both lines that identify a run to its caller carry the order they belong to:
340
- `RUN=<path> order-id=<id>` and `ATTACH=<path> order-id=<id> started=<time>`. A reply naming a run
341
- cannot be checked against the order that was placed unless the id travels with the folder.
342
- - Two structural guards that make the above regressions loud instead of silent: the install plan
343
- must stay isomorphic to the shipped home image, and every registered hook must actually start as
344
- a child process with its imports resolved.
345
- - `doctor` compares the version answering a hook against the installed copy, verifies the installed
346
- agent definitions against the repository, and downgrades to a warning instead of reporting code it
347
- never ran as healthy.
348
-
349
- ## [0.5.1] - 2026-08-12
350
-
351
- ### Fixed
352
-
353
- - Installing or uninstalling no longer fails over a rules-registry lock that is busy for one more
354
- millisecond. Windows answers a taken lock with three codes, and only one of them is `EEXIST`: a
355
- lock file whose delete is still pending comes back `EPERM`, and a file another process still
356
- holds comes back `EBUSY` — the acquisition loop treated both as fatal instead of waiting. All
357
- three now mean "not yours yet", and the decision lives in one exported check, `isLockTaken()`.
358
-
359
- ### Changed
360
-
361
- - The README's first screen names the problem the package solves, keeps the install command inside
362
- the first 30 lines, adds a second badge row, and shows `doctor` output from a healthy host in
363
- place of a screenshot.
364
- - The reference documentation under `docs/` is English-only now (`overview.ru.md` became
365
- `overview.md`), and the working folders — plans, checklists and audits — left the repository and
366
- its history: a fresh clone contains the five reference documents and nothing else.
367
-
368
- ## [0.5.0] - 2026-08-11
369
-
370
- ### Added
371
-
372
- - A fifth host guard now catches `TaskStop` while a live Codex run is still writing to the
373
- repository, names the run and points to `codex-bridge stop <run>` before allowing a repeated
374
- request. `doctor` and `projects` also surface active runs, so stopping a dispatcher wrapper is
375
- no longer mistaken for stopping its detached worker.
376
- - `codex-bridge hook <name>` gives Claude Code a stable way to invoke each installed guard, and
377
- `meta.json` now records the package version that produced a run. The usage report can therefore
378
- separate historical runs made outside this runner from runs that should satisfy its sandbox
379
- contract.
380
-
381
- ### Changed
382
-
383
- - **Breaking:** the installed package layout now uses `agents/codex-bridge/` instead of
384
- `agents/codex/`, `commands/codex-bridge/` instead of `commands/codex/`, and
385
- `~/.lyupro/.codex-bridge/lib/` for the runner and guards. Slash commands are now
386
- `/codex-bridge:usage` and `/codex-bridge:env`, and hooks in `settings.json` use
387
- `codex-bridge hook <name>` when the matching package version is available. Run
388
- `codex-bridge update` to migrate an existing installation and retire the old layout.
389
- - Runs started without an explicit `--slug` now derive a readable folder name from the required
390
- order id instead of grouping unrelated work under the dispatcher name. Existing run folders
391
- remain discoverable by their order id and task fingerprint.
392
- - The README now leads with the package's guarantees, verification and update guidance, and
393
- limitations before the command reference, and the repository now publishes a security policy
394
- and private vulnerability-reporting route.
395
-
396
- ### Fixed
397
-
398
- - Guards invoked through a global-install symlink no longer exit successfully without running;
399
- the direct-invocation check now compares resolved files, so a silent guard cannot allow every
400
- operation while `doctor` reports it healthy. Hook registration also chooses the short command
401
- form only when the command on `PATH` is this package's exact version, rather than merely present.
402
- - Retiring the previous layout no longer removes the shared Claude Code `agents/` and `commands/`
403
- directories. Seeded operator files are moved only after byte verification, and an interrupted
404
- migration is retried by the next `update` even when the new files are already current.
405
- - A continuation grant that loses its `--continue` flag is now refused before attachment instead
406
- of replaying an older run's verdict as though new work ran. Attachment output also states
407
- explicitly that no new work was started.
408
- - The test suite now isolates both installation roots, preventing tests from writing fixtures or
409
- seeded configuration into the operator's real `~/.lyupro/.codex-bridge/` directory. Migration
410
- dry runs also name the hook event and matcher instead of printing `undefined`.
411
-
412
- ## [0.4.0] - 2026-08-09
413
-
414
- ### Added
415
-
416
- - Scope patterns are checked before Codex starts. A pattern that cannot match anything — an
417
- absolute or drive-qualified path, backslash separators, a `..` segment, or a spelling no
418
- repository file answers to — is refused with no run folder and no quota spent, for all three
419
- dispatchers. The file list comes from `git ls-files --cached --others --exclude-standard`, so
420
- ignored output does not count as repository content. A file the run is meant to create is
421
- declared with `--scope-new`, accepted only by `codex-build` and recorded in `worker.json`.
422
- - `codex-bridge permissions` reports, adds, and removes the optional shell permission rules for
423
- both CLI names and the clone entry point. The exact allow and deny strings merge into
424
- `settings.json`, preserve foreign rules, and are also removed by `uninstall`; `doctor` reports
425
- an absent or partial set as an optional warning.
426
-
427
- ### Changed
428
-
429
- - `codex-bridge sweep` is now `codex-bridge unlock`. The default scope is the current repository;
430
- a bare project name remains addressable, and `--all` is now required for the whole store. The
431
- report shows age, silence, and `alive` / `dead` / `foreign` / `unverified` identity verdicts;
432
- confirmed-alive records are refused with `codex-bridge stop <run>` instead of being closed.
433
-
434
- ### Fixed
435
-
436
- - Run records now verify that a pid still belongs to the process that started the run, using the
437
- process start time and a fresh heartbeat before falling back to an OS probe. A reused or
438
- unverified pid is never signalled by `stop`; `codex-bridge unlock` remains the non-killing way to
439
- close a record whose process identity cannot be confirmed.
440
- - JSON files written by PowerShell with a UTF-8 BOM are now read consistently by the CLI and
441
- installed hooks, and one command run creates at most one settings backup without deleting older
442
- backups.
443
- - A run a guard refused before Codex ever started no longer costs the next attempt a round trip. Such a folder now closes as `aborted_pre_start` instead of the same `failed` a twenty-minute paid run gets, and the chain gate, the continuation limit and the tree baseline all skip it: the same order id starts as a first run, silently, with no `continue:` grant. Reported from a parallel `codex-build` launch — the second run was correctly refused with "two writing runs in one tree are prohibited", and its one-millisecond folder then made the retry demand a grant for work that never happened. Folders written before this release are recognised by what `meta.json` records — `exit`, `session_id`, `events_bytes`, `stderr_bytes`, `tokens_reported` — rather than by whether `events.jsonl` is still on disk, because retention deletes that file from paid runs too. The folder itself is kept: it is the audit trail of who was refused and why.
444
- - `chainBaseline()` takes the first run of the chain that actually started, so a pre-start folder sorting first no longer leaves the next pass without the `state-before.txt` its work is measured against.
445
- - `uninstall --dry-run` names each hook by its own matcher. Three of the four hooks are PreToolUse,
446
- and a lookup by event alone reported `Agent|Task` for the worktree lock and the prune guard too —
447
- in the one line an operator reads to see what a removal is about to touch. The removal itself was
448
- never affected: hooks are found by command, not by matcher.
449
- - The order gate's refusal now shows the line it found. A caller who wrote
450
- `scope (you may create/modify ONLY these):` was told scope was missing, re-read their own prompt,
451
- saw it written there, and had nothing to act on; an autonomous run rewrites the same text forever.
452
- The refusal names the candidate line under the input it belongs to and says why it was not
453
- accepted — a qualifier between the label and the colon, a value that is still a placeholder, or no
454
- separator at all — and stays silent when no candidate exists, because then "the input is missing"
455
- is the truth. The parser accepts exactly what it accepted before: deriving scope from prose would
456
- give away the only guarantee that a run cannot touch files nobody listed.
457
-
458
- ## [0.3.1] - 2026-08-07
459
-
460
- ### Added
461
-
462
- - The CLI answers to `codexb` as well as `codex-bridge`, and README now documents the global install that puts both names on `PATH`. Every spelling that can reach deletion comes from one frozen list, and the prune guard builds its matcher from that list rather than repeating the names — two independent lists drift silently, which is exactly how the installer and test hook lists had already drifted. The full name stays primary: the docs, checklists and three agent prompts are written with it.
463
- - Every run carries the rules it is judged by. A seeded `conventions.md` beside `run-config.json` and an optional `.codex-conventions.md` in the repository being worked on are pasted into `task.md` verbatim, under one `## Conventions` section. Either file missing is not an error, and a blank one produces no section at all — rules handed over as an empty heading read as rules ignored. Until now project rules reached a run only when the orchestrator remembered to write "read CLAUDE.md" into the task, and six acceptance passes in a row caught the same class of violation: the rule existed, applied, and had not travelled. Pasting rather than referencing is the point — "read file X" is hope, and a run is free not to.
464
- - `doctor` opens with the path of the package copy answering, marked `clone` or `installed package`, and reports whether the host conventions file was found, warning when it exists but is empty. A global install puts a second copy of the package beside any clone, and `update` copies host files from whichever copy launched it — so two diagnoses of the same host were previously indistinguishable.
465
-
466
- ### Fixed
467
-
468
- - `codex-bridge projects` printed the `last run` column on two clocks. Folder names are written in local time and `meta.finished_at` is an ISO stamp in UTC, and each reached the column through its own branch, so a run whose folder says `144243` was listed as `12:46` — the operator's own zone offset, indistinguishable from a different run. Which run is newest was never affected, only how it printed.
469
-
470
- ## [0.3.0] - 2026-08-07
471
-
472
- ### Added
473
-
474
- - `codex-bridge sweep [<project>]` closes running records whose runner pid is dead, across the whole store or one project. It changes state but deletes no run folder or transport, never touches a live pid, and lists every stalled-but-live run with the exact `codex-bridge stop <run>` line that ends it. A dead record can therefore be cleared without paying for another run, while a live worker remains the sole writer until `stop` kills it first.
475
- - Every new run folder carries a `heartbeat` file, and `status.json` carries `stdio_drained`. The heartbeat records movement from Codex output and the still-running child rather than a timer that would keep an orphan alive; `stdio_drained` says whether stdout/stderr closed normally or the bounded drain grace had to end the run.
476
-
477
- ### Changed
478
-
479
- - Hook liveness now requires `state: running`, a live runner pid and a heartbeat no older than five minutes. A stalled run therefore stops holding the worktree lock instead of holding it until the deadline; the refusal that remains — always about a working run — says how long ago it last made progress and prints `codex-bridge stop <run>` for taking the repository back early. Record cleanup and the second-writing-run check still judge by pid alone, so a stale but live run is released to the operator, not closed underneath its worker.
480
- - The worker closes a run after `exit` with a bounded 30-second grace for stdio drain instead of waiting for `close`, which a grandchild can hold open forever. Healthy runs keep the event tail that carries their result and record `stdio_drained: true`; only the fallback records `false`.
481
- - The three dispatcher prompts now open with one shared no-self-execution block rendered through `{{CODEX_NO_SELF_EXECUTION}}`. If `run-codex.mjs` does not start a run, the dispatcher must return `FAIL` and stop, and a reply with no run folder is blocked by the guard. The installed prompts change only when the host runs `codex-bridge update`; a host that does not update keeps the old prompts.
482
-
483
- ### Fixed
484
-
485
- - A dispatcher that cannot start Codex can no longer read the code, write the files or fix the tests itself and claim a result. The guard also rejects a made-up reply with no `RUN=` or `ATTACH=` behind it: no run, no result.
486
-
487
- ## [0.2.0] - 2026-08-06
488
-
489
- ### Added
490
-
491
- - `codex-bridge projects` gives the run store a way to look at itself: one line per project — runs, weight, tokens spent, how many are running right now, when the last one started — and one line per run under a project name, plus `--json` for scripts. The store had grown to 12 projects, 152 runs and 80 MB with no command that could answer "what has Codex been working on"; the only way in was a file manager. The count of live runs is computed by the same module the reply guard and the worktree lock use, because a second detector of a live run sitting next to the first would drift from it silently.
492
- - `codex-bridge prune` reclaims that space, and it is built to be hard to fire by accident: without `-f` it prints a plan and deletes nothing, `-f` still asks before each step, and with no terminal it refuses outright — deletion is an operator action, and the bypass flag that would undo this is deliberately absent. A gentle prune removes transport only (event streams, stderr, the archived `raw.log` of older runs) and leaves accounting, reports and worktree snapshots in place, so a task can still be continued and still be read after its megabytes are gone; `--purge` is what takes a folder whole, and it is refused together with `--all-projects`, because deleting a dozen projects with one command has no second line of defence. A live run is never a target — it is recognised by its process, not by a status file, since the file that would be deleted is the verdict being written right now.
493
- - Run transport is dropped automatically from runs older than 30 days when a new run starts, and the dispatcher's reply says how much was freed: a rare event that goes unexplained sends someone looking for a fault. Automatic cleanup can only shed weight — the code to remove `meta.json` or a folder does not exist on that path at all, so it cannot be switched on by mistake — and `retention` in `run-config.json` changes the age or turns it off. Both `install` and `doctor` say out loud that it is on, because an installer that quietly starts deleting files is a surprise, not a convenience.
494
- - A fourth hook refuses `codex-bridge prune` in an agent's shell command before the deleting process exists. Mentioning the command is not calling it: heredoc bodies and quoted text are stripped before the check, after the guard rejected the very commit that introduced it over a command name in the message. `git prune` and `npm prune` are left alone — a guard that breaks unrelated work is a guard that gets removed, and then it protects nothing.
495
- - `codex-bridge read <run>` renders a run's events as text on demand. Readability is now something the package produces when asked rather than a file it keeps, and a rendering that is not stored cannot disagree with the facts.
496
-
497
- ### Changed
498
-
499
- - Runs go through `codex exec --json`, and `events.jsonl` is the single source of the quota signal, the token count, the session id and the failure reason. The stream is written by the CLI, not by the model, so a quotation out of a source file can no longer reach it. `tokens` is input plus output with the CLI's whole `usage` object beside it, and `session_id` comes from `thread.started` instead of a line matched in a log.
500
- - The reason for a `FAIL` is taken from the run's own error event rather than from `stderr.log`. A live probe showed `stderr.log` is not empty even for a clean run — 488 bytes of execpolicy refusals, repository paths included — which made the reason a random blocked call instead of the real one. The transport branch keeps the reason the CLI itself gave with its status code: a `LIMIT` whose reason does not mention the quota is the same class of defect.
501
- - A run that never began is one with no events *and* no stderr. Stderr with no events is a CLI that did not understand a flag — a different diagnosis, calling for a different action, and the two no longer collapse into one.
502
- - `log_bytes` in `meta.json` is replaced by `events_bytes` and `stderr_bytes`.
503
-
504
- ### Removed
505
-
506
- - `raw.log` is gone. Under `--json` it would have been a byte-for-byte copy of `events.jsonl` — a second source of truth with no role of its own — and the package already declared it ephemeral and safe to delete while resting the "abandoned at start" verdict on it. An artifact one is allowed to remove cannot be the ground of a verdict.
507
- - The text detector of the quota signal is removed entirely rather than kept as a fallback. While the regular expressions live, so does the class of defect they caused.
508
-
509
- ### Fixed
510
-
511
- - `LIMIT` is set only by an error event from the CLI carrying `status: 429` or an error type naming the limit. A `codex-build` run that had finished its work grepped the package's own test fixtures, printed a line where `ERROR` and `rate limit` stand together, and was closed as quota-exhausted — the work was complete and the suite was green, and the verdict threw it away.
512
- - A run killed by its time budget says so. It records `stopped_on_deadline` and answers `FAIL — run stopped on its deadline`, instead of inventing a reason out of whatever text was nearest.
513
- - A log file that cannot be written no longer leaves Codex orphaned. An unhandled stream error used to kill the worker while the CLI kept burning quota with nothing left to close it; now either stream failing takes down the process tree, so the run dies with its process rather than instead of it.
514
- - A run that exited honestly is no longer marked as killed by its deadline. The timer judged by `close`, which a grandchild process can hold open long after the run is over; it now watches `exit`.
515
- - A verdict is never shortened to fit the terminal. In a narrow window `running` was printed as `…nning`, which reads as a damaged run rather than one still in flight; a verdict is one word from a known set, like a number or a date, and the run name is what a narrow table gives up instead.
516
- - The CLI's own warnings are no longer read as the model's complaint about the order. `codex-cli` announces a deprecated configuration key through the same event shape the model uses for a content error, before the turn even opens — so a failed run on such a host would have answered "`[features].codex_hooks` is deprecated" when asked why it failed. Only events after the first `turn.started` are the model's; a complaint about the order cannot predate the model's first thought.
517
-
518
- ## [0.1.4] - 2026-08-05
519
-
520
- ### Added
521
-
522
- - A continuation has to prove the orchestrator ordered it. `--continue` is refused unless the task text carries a `continue:` grant naming the run being continued and why, and the refusal costs nothing: no run folder is created and Codex is never started. The grant is single-use by construction rather than by a counter — continuing the last run of a chain appends a later one, so the same line stops matching by itself. Until this release a dispatcher could decide on a second pass alone: one did, spent 75 691 tokens of the Codex subscription and created a module the order never asked for.
523
- - A reply that stays silent about a live `codex-build` run of the same project is blocked. The guard reads the project's runs folder from disk instead of trusting the paths the reply chose to mention, so a second run can no longer be left working in the background while the verdict of the first is handed back. Runs of `codex-scout` and `codex-review` are not counted: they hold a read-only sandbox, and blocking a reply over one would spend the guard's budget on a run that touches nothing.
524
- - A `PreToolUse` lock refuses file edits inside a repository held by a live `codex-build` run. Knowing that a run is live is not the same as being unable to disturb it — edits made during that window land in the runner's before/after snapshot and fail an honest run for work it never did. An edit made through `Bash` still walks past the lock; parsing a command line is not something a guard can do reliably.
525
-
526
- ### Fixed
527
-
528
- - `doctor` names the file each hook was registered for. With two hooks on one event it matched the installation record by event alone and reported the worktree lock's matcher as pointing at `order-gate.mjs` — the one line where an operator can check what is installed, showing the wrong file.
529
- - Hook presence is decided by this package's own command rather than by the matcher it currently sits under. The matcher is generated from the list of host tool names and grows whenever a spelling is added, so a host installed under the previous matcher would collect a duplicate hook on update and keep a hook pointing at a deleted file after uninstall.
530
-
531
- ## [0.1.3] - 2026-08-05
532
-
533
- ### Added
534
-
535
- - Dispatcher calls are refused before a subagent exists. A `PreToolUse` gate rejects a `codex-*` call whose task text carries no order label — and, for `codex-build`, no scope — or whose value is still a template placeholder. Until this release the missing input was discovered by the runner, after the subagent had been created and paid for out of the Claude subscription.
536
- - `codex-build` declares the outcome of its own run in a required `outcome` field. A run that could not do the work reports `fail`, and the verdict goes red with the reason even when the worktree is clean and verification is green — the shape that used to answer `OK`, because an empty delta is a legitimate outcome the runner cannot tell apart from a failure. Whether a run owed that declaration is read from the response schema it was handed, so runs recorded before this release keep the verdict of their own day.
537
-
538
- ### Changed
539
-
540
- - Every dispatcher's description and prompt lists the inputs it requires, filled in at install time from the same table the gate enforces, and the runner's own refusals name the input, an example value and what to do about it. A caller no longer learns the contract by being refused.
541
- - A failed `codex-build` reports what it left in the worktree, the way a quota-exhausted run already did. "The work was not done" is not "the tree is clean", and where no snapshot exists the line says so instead of claiming there were no changes.
542
- - The installation record stores a list of hooks rather than a single hook, so a host can register more than one. Records written by earlier versions are read as a one-element list.
543
-
544
- ## [0.1.2] - 2026-08-04
545
-
546
- ### Fixed
547
-
548
- - A damaged ownership registry no longer breaks the command that reads it. Every command parses the registry before its first write, `doctor` reports the damage as a failed check while still printing the other eight, and `uninstall` finishes removing the package but keeps the shared rules file, because ownership is unknown and disarming another installation is worse than leaving a file behind. Until this release an `uninstall` on a damaged registry stopped after the hook was gone, leaving the host with every package file and no reply guard.
549
- - Owner updates are written through a temporary file under a lock, so two installers running at once merge instead of overwriting each other. The lock expires: an interrupted run used to leave it behind, and every later install and uninstall then died on it until someone deleted the file by hand.
550
- - Installation claims its share of the rules file before writing anything. Claiming it last meant a failure at that step left a fully installed host missing from the registry, and the next uninstall elsewhere would delete the rules out from under it.
551
-
552
- ## [0.1.1] - 2026-08-04
553
-
554
- ### Fixed
555
-
556
- - Two runners starting at the same moment in equally named repositories no longer fail on the exclusive writes that assign a runs directory: the runner that loses either race now reads the marker the winner wrote and takes the next candidate, which is the collision the marker was introduced to survive.
557
- - A repeated order is answered from its newest run instead of the pass before it: while a continuation was in flight, repeating the command returned the previous run's verdict as if it described the current one.
558
- - Uninstalling one installation no longer takes the Codex rules file away from every other installation on the machine. The file lives in `CODEX_HOME`, which is shared by every host, while ownership was recorded per host and two installations of the same version share a fingerprint — so removing a project-scope or throwaway host silently left the remaining ones running Codex without the execpolicy that forbids destructive repository work. Ownership is now tracked next to the file itself, and the last owner to leave is the one that removes it.
559
-
560
- ## [0.1.0] - 2026-08-04
561
-
562
- ### Added
563
-
564
- - Three dispatcher agents for reconnaissance (`codex-scout`), scoped implementation (`codex-build`), and independent review (`codex-review`), each with a structured response contract.
565
- - A delegating runner that separates the launcher from a detached worker, preserves run artifacts after a dropped call, and supports configurable artifact roots.
566
- - A zero-dependency `codex-bridge` CLI with `install`, `uninstall`, `update`, and `doctor`, including user/project scopes, dry runs, force handling, idempotent installs, hook merging with backups, and manifest-based ownership tracking.
567
- - A reply guard hook that blocks dispatcher replies that do not match the recorded run state or required response shape.
568
- - Run-chain and busy-tree lock protections: repeated work is linked across runs, an unfinished chain requires explicit `--continue`, and concurrent writing runs for one repository are refused before Codex starts.
569
- - Scope and coverage validation: implementation changes must stay within the declared scope, report/worktree mismatches fail the run, and reconnaissance reports must account for all requested subquestions.
570
- - Package-managed Codex execution rules that forbid history-, branch-, index-, and worktree-destructive operations, writes inside `.git`, and other unsafe repository mutations while leaving the operator's existing rules untouched.
571
- - A 400-line source-file size gate with explicit rationale required for exclusions.
572
- - Asynchronous run start: the launcher reports the run folder and returns instead of holding the caller for the length of the run, and repeating the same order attaches to the run already in flight rather than paying for a second one.
573
- - Per-mode time budgets that end a run and its whole process tree at a deadline, plus a `stop` subcommand for closing a run that wedged anyway.
574
- - Explicit reconnaissance subquestions through a mandatory, repeatable `--question` flag, so coverage is counted against what was ordered rather than against prose the dispatcher rewrote.
575
- - An `answerLanguage` configuration key that fixes the language of a run's answer instead of leaving it to the model.
576
-
577
- ### Changed
578
-
579
- - Run identity now includes an orchestrator-issued mandatory `--order-id`, stored with run state and used alongside the task identity to detect repeats even when a run is renamed.
580
- - A repeat of an order is now continued at most once, and only behind a run that already carries a verdict, so a dispatcher can no longer restart itself into a chain of paid runs.
581
- - A run left behind by a dropped call is now closed with a `FAIL` verdict that names what it left in the worktree, instead of staying open with no account of its work.
582
- - Runs now snapshot both branch and `HEAD` before and after execution; branch changes, detached `HEAD`, and commit drift are treated as failures, with preflight refusal for a subsequent run when an abandoned run left repository state out of sync.
583
- - English-language prompts and runner/command output are shipped for the three dispatchers and their supporting commands.
584
- - Installation updates track installed file contents so outdated package files can be refreshed without overwriting hand edits unless `--force` is requested.
585
-
586
- ### Fixed
587
-
588
- - Prevented environment and service writes from being misreported as delegated work while keeping them visible in run results.
589
- - Prevented different repositories with the same directory name from sharing run history.
590
- - Prevented malformed state files, including files with a byte-order mark, from silently bypassing abandoned-run detection, busy-tree locking, and chain lookup.
591
- - Fixed diagnostics to report the run folder actually used by the runner and to continue reporting remaining checks when a project marker is unreadable.
592
- - Fixed reported file paths to keep the spelling on disk instead of the case-folded form used for matching, so a reply names a path that exists on case-sensitive filesystems.
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [0.6.4] - 2026-09-21
8
+
9
+ ### Fixed
10
+
11
+ - A refusal that spends no quota no longer leaves a run folder behind. A second writing run against a
12
+ busy tree was refused correctly, but its folder was created first — and in a repository that hosts
13
+ the run folders, the live writer's witness then spent every tool call ordering the orchestrator to
14
+ revert a directory the tool itself had made, or to stop an honest run. Neither is a safe action. The
15
+ misplaced check was the symptom: refusals sat on two sides of the registration boundary with no rule
16
+ about which side was which, and two gates of the same class already disagreed — a dead sandbox
17
+ refused without a folder, a busy tree with one. The quota-free host checks now live in a module that
18
+ is handed no run directory, so a check added there cannot register a run, and a table test holds
19
+ every refusal with its side and the reason for that side. A busy tree and an unavailable Codex CLI
20
+ therefore write nothing at all: they no longer produce an `aborted_pre_start` folder, and their
21
+ exit code stays 1, because the order was correct and the host or the tree was not.
22
+ - The live witness and the final verdict judge one worktree by one definition. The witness read
23
+ `git status --porcelain` on its own while the verdict reads the recorded snapshot, so three kinds of
24
+ change were visible to one and invisible to the other: the run's own folder, the orchestrator's
25
+ environment writes, and gitignored working notes. The witness now composes the same chain as the
26
+ verdict, and its message says first that those three are already excluded — so what it lists is the
27
+ orchestrator's own edit — and only then offers the two actions. A structural test fails any hook that
28
+ spells a worktree query in an argument list; this drift went unnoticed for months and surfaced only
29
+ as a false accusation against a run that was doing its job.
30
+ - A file moved inside its own scope is no longer a stray. The shared snapshot was read with git's
31
+ rename detection on, which prints one row spelled `old => new` — not a path, and matching no scope
32
+ pattern. The witness named that token at the orchestrator, and the verdict would have failed an
33
+ honest build for a `git mv` it was told to make. The snapshot is now read with `--no-renames`: a
34
+ rename is a deletion plus an addition, two real paths, each judged on its own merits.
35
+ - A scope pattern that can only name a directory is refused before the run starts. A scope is compared
36
+ against file paths, never directories, so `muse/scripts` authorises one file that will never exist
37
+ while everything created inside the folder counts as a stray. Declared patterns escaped this by luck
38
+ — `git ls-files` prints no directories — but `--scope-new` is exempt from the existence check by
39
+ design, and a run ordered with `--scope-new muse/scripts` worked ten minutes, returned a filled
40
+ result, and was failed for the three files it created there. A trailing slash, a name that is a
41
+ directory on disk, and a glob-free name whose last segment has no extension are now refused before
42
+ the run folder exists, with the spelling that works named in the refusal. Patterns containing a glob
43
+ are never judged by these rules.
44
+
45
+ ## [0.6.3] - 2026-09-17
46
+
47
+ ### Added
48
+
49
+ - A dead Codex sandbox refuses the run before it spends quota. On 2026-09-16 an unclean Windows
50
+ shutdown left the sandbox helper's state file as 22 zero bytes, and every delegated run after it
51
+ paid for a session in which not one command ran — one scout even came back `OK`. The launcher now
52
+ asks the sandbox to echo a marker before the run folder exists, and calls it dead only on double
53
+ evidence: the role's own flags and a flagless control both produce no marker, neither reports an
54
+ argument error, and the CLI still answers `--version`. Codex had already changed the shape of its
55
+ own sandbox command once, so anything the probe cannot judge lets the run start and is named in
56
+ the reply — a release that stops accepting the package's sandbox flags cannot turn the check off
57
+ silently. `stderr` is quoted for the operator and never parsed. The probe is bounded on every
58
+ path: a hung attempt stops the whole Codex tree rather than the `cmd.exe` in front of it, and a
59
+ capture that overflows is inconclusive, never dead. Windows and Linux are judged, each after both
60
+ outcomes were seen live on it; on Linux, where a dead sandbox is the ordinary state of a fresh
61
+ Ubuntu 23.10+ server, the refusal names the AppArmor profile that repairs it and warns against
62
+ lifting the restriction through `sysctl`. macOS is skipped — no outcome has been observed there.
63
+
64
+ ### Fixed
65
+
66
+ - A scout that executed no command can no longer come back `OK`. On the dead sandbox of 2026-09-16
67
+ a scout was graded `OK` without reading a single file: its evidence explained that the files were
68
+ unreadable, and the coverage gate only checked that the evidence was a non-empty string. The
69
+ verdict now requires a fact before it judges coverage — at least one completed
70
+ `command_execution` in `events.jsonl`, with any status or exit code. Refused calls never enter the
71
+ stream, which is what makes the fact reliable; across 99 earlier successful scout runs it was
72
+ missing exactly twice, both on that sandbox.
73
+ - Quoted text no longer reads as shell commands to the worktree lock. The write-intent scanner read
74
+ quotes three different ways, and its raw split on `;` and `|` cut inside them: an `awk` script
75
+ containing "Do not touch any file outside" was refused on 2026-09-16 as a write to `any`, `file`
76
+ and `outside` — the fourth refusal on one root after `node -e`, `sed` and a heredoc body. One
77
+ lexer now owns quotes, escapes and command boundaries for all three consumers, drops comments, and
78
+ opens subshells and `$(…)` only where the shell does.
79
+ - An argument that happens to be a command name is not a write. `git log --grep rm -- README.md` and
80
+ `grep -rn touch src/` were refused while a build held the repository, because the scanner looked
81
+ for `rm` and `touch` in any word. A command name now counts only in command position — after
82
+ assignments, reserved words, leading redirections, a short list of launchers, and a `find -exec`
83
+ action up to its terminator.
84
+ - A stop at the deadline cannot block the launcher forever. `stopCodex()` gave `taskkill` no timeout,
85
+ so a stalled `taskkill` held the event loop and every settlement timer with it. It now has ten
86
+ seconds, after which the direct child is killed.
87
+ - The response guard judges a run by its own process, not by a bare pid. Without the run folder or
88
+ the recorded start time, a pid reused by another process after a reboot read as "the run is still
89
+ working", and the guard held the dispatcher on a dead run until the session's retry budget stopped
90
+ the session.
91
+ - The project list no longer prints the raw `running` of a run whose worker died before writing
92
+ `meta.json`. A row without a verdict shows what closing the run would write: `abandoned` for a
93
+ dead or foreign process, `unverified` when the end cannot be proven. The "live now" column keeps
94
+ its fresh-heartbeat policy.
95
+
96
+ ### Changed
97
+
98
+ - One module decides whether a recorded run is still live. Seven places answered that question on
99
+ their own through pid-first wrappers whose other inputs were optional — which is how the response
100
+ guard came to ask with a bare pid and the project list never to ask at all, although
101
+ `markAbandoned()` already held the exact rule. `src/home/lib/meta/run-liveness.mjs` now owns it,
102
+ and `tests/one-liveness-judge.test.mjs` fails the suite when another module asks a process
103
+ identity, re-exports one under a new name, or declares a pid-first wrapper again.
104
+ - The rows a reply carries are rendered by one function. The Retention row had been copied into five
105
+ reply functions and was still missing from the runner's failure path, so a runner failure lost it;
106
+ a sixth copy for the sandbox row would have repeated that defect.
107
+
108
+ ## [0.6.2] - 2026-09-07
109
+
110
+ ### Fixed
111
+
112
+ - No command greets the operator with a deprecation warning of its own. On Node 24 every `install`,
113
+ `update` and `doctor` printed `[DEP0190] DeprecationWarning: Passing args to a child process with
114
+ shell option true` ahead of its useful output, because the check that asks the command on PATH
115
+ for its version ran through a shell — the only way to execute the `.cmd` shim npm writes on
116
+ Windows. The shim is now located on PATH and executed directly, and the interpreter comes from
117
+ `ComSpec` instead of the PATH handed in, so a trimmed PATH cannot turn the check into a silent
118
+ "no version".
119
+
120
+ ### Changed
121
+
122
+ - A guard fails the suite if a child process in `src` or `cli` is started through a shell again:
123
+ the `shell` option may only be the literal `false`, and `exec`/`execSync` may not be imported at
124
+ all. What this release fixes was not spelled `shell: true` but `shell: process.platform ===
125
+ 'win32'`, so a guard bound to the literal would have missed the line it exists for.
126
+ - The hook-registration test that claimed to cover a command of another version handed a directory
127
+ holding no `codex-bridge` and exercised the unreachable branch instead. It now runs a fixture
128
+ shim in a directory named with a space, brackets and an ampersand, covering both branches and,
129
+ for the first time, that a version is read at all.
130
+
131
+ ## [0.6.1] - 2026-09-07
132
+
133
+ ### Fixed
134
+
135
+ - `update` names the package it acted from. Standing in a clone while an older copy answers from
136
+ PATH, the command compared the home against its own files and said `codex-bridge is up to date` —
137
+ true about the package that ran, silent about the one on screen, and the cause had to be found by
138
+ hand. Every outcome now carries the name, version and path of the copy that answered, and when
139
+ that copy is not the checkout the operator is standing in, a second line names both versions and
140
+ the command that changes which one answers.
141
+ - `permissions add` reports the state of the rule set instead of the size of its own change. A
142
+ complete set answered `Added 0 permission rule strings.`, where zero reads as a refusal rather
143
+ than as "all of them are already there".
144
+
145
+ ## [0.6.0] - 2026-09-07
146
+
147
+ ### Added
148
+
149
+ - `codex-bridge model` answers which model and reasoning depth each of the three roles runs on, and
150
+ where each value comes from — the operator's config file or the package default. The config is one
151
+ file per machine, shared by every project on it, and the command says so: changing a model here
152
+ changes the executor for every repository this host delegates from.
153
+ - `codex-bridge model list` prints the live catalogue, hidden models included and marked as hidden.
154
+ A model Codex does not advertise is still a model the operator may choose; omitting it would mean
155
+ deciding for them and then explaining why the system has a model the list denies.
156
+ - `codex-bridge model set <role> <model> [effort]` and `model unset <role>` write that choice. The
157
+ pair is checked against the catalogue entry of that very model at the moment of writing, so
158
+ `ultra` — supported by the newest model and rejected by the package's own flat list until now —
159
+ became reachable, and a depth the chosen model does not offer is refused with its supported
160
+ depths named. Nothing is substituted silently: the command never changes what it was not asked to.
161
+ - `codex-bridge model speed <role> <tier>` pins a paid acceleration tier, and `model speed <role>
162
+ unset` removes it. The tier is spelled with the identifier the catalogue reports for that model,
163
+ never a word held in the package, and the command writes nothing until a second call ends in
164
+ `confirm` — the first prints the catalogue's own description of the tier and a link to the page
165
+ carrying the credit multipliers. The installer now also places one `ask` rule of its own, so the
166
+ host asks the operator before an agent can run that command at all.
167
+ - A pinned tier reaches the run: the launcher passes `-c service_tier=<id>` when, and only when, a
168
+ role has one pinned.
169
+
170
+ ### Changed
171
+
172
+ - The depth of a run is no longer validated against a list held in the package. That list had
173
+ drifted from the server in both directions — it offered a depth no model accepts and refused one
174
+ the newest model supports — so the writing command asks the catalogue and the hot path checks only
175
+ the shape of the value. The arbiter at run time is Codex itself, whose refusal is more accurate
176
+ than a copy of its data.
177
+ - One boundary now owns every write to the run config, and it persists only the edit it was given.
178
+ Reading merges the package defaults, and the previous writer saved that merged object, so a single
179
+ edit would have frozen every default into the operator's file — worst of all the list of
180
+ environment paths, which the package extends as it learns of new ones and which decides whether a
181
+ change belongs to the run or to the environment.
182
+ - The word `mode` no longer names three different things in this package. The reviewer's flag is
183
+ `--changeset`, the prune says which strategy, the table which truncation, and one registry answers
184
+ who the three agents are.
185
+ - The README states the suite size it actually has.
186
+
187
+ ### Fixed
188
+
189
+ - Two edits of the config racing each other can no longer lose one of them. Both commands read the
190
+ role profiles, waited seconds for the catalogue, then wrote what they had read, and three
191
+ concurrent processes all reported success while one edit was missing from the file. The edit
192
+ boundary now reads the file itself, hands the fresh value to the caller's transformer, compares
193
+ the bytes immediately before publishing, and holds the lock this package already used for the
194
+ rules-ownership registry — one definition now serves both writers.
195
+ - The worktree lock reads the shell rather than the prose someone wrote into a document. A plain
196
+ `cat > file << EOF` handed the whole command, heredoc body included, to the analysis, so the word
197
+ `touch` inside an English sentence made the next words look like files being written. The reverse
198
+ hole closed with it: the tail after a heredoc's closing marker was not parsed at all.
199
+ - A dead run stays dead even when Windows hands its process id to someone else.
200
+ - The reader of the Codex user configuration answers about configuration, not about prose inside it.
201
+ A multi-line TOML literal could contain a line indistinguishable from an assignment, and it was
202
+ read as the setting; an empty argument threw past the module's own error boundary, which exists so
203
+ the table renders whatever state the file is in.
204
+
205
+ ## [0.5.5] - 2026-08-28
206
+
207
+ ### Fixed
208
+
209
+ - The write guard now reads quoting, so a redirect character inside a quoted argument is text rather
210
+ than an operator. While a delegated run held the repository, `sed -E 's#(A ).*#<redacted>#'` was
211
+ refused as a write to a file named `#'`: the scan ran over raw text, so the `>` looked like a
212
+ redirect and the token after it looked like its destination. The file already carried a defence
213
+ against this class, added after a `node -e` expression was misread the same way, but that defence
214
+ filtered candidates holding characters no file name may contain and the one refused here held
215
+ none. The guard still fails closed: when quotes cannot be balanced the scan behaves exactly as
216
+ before, because answering "no write here" to a line it cannot parse would be worse than the false
217
+ refusal being removed.
218
+ - The installation summary no longer reads as a bug. Four `PreToolUse` guards printed as
219
+ `PreToolUse and PreToolUse and PreToolUse and PreToolUse` because the sentence joined every hook's
220
+ event; it now groups by event and counts.
221
+ - The installed-file count agrees with `doctor`. The summary counted the Codex rules file among the
222
+ files while `doctor` reports it on its own line, so `68` sat next to `67` and read as a lost file.
223
+ - `Granted 0 permission rule(s)` no longer reports the good outcome as if it were a failure. When
224
+ the set is complete the summary says so; on a host whose settings were edited by hand it says how
225
+ many rules are missing and how to restore them, instead of claiming all are in place while the
226
+ operator gets permission windows. The already-installed branch reports the set by reading it —
227
+ granting there would have restored a deleted rule under the heading "nothing to do".
228
+ - A contract probe that refuses to judge now carries the host's own words. `The host exited with
229
+ status 1.` was the entire account of a failure on a live host; the last non-empty line the host
230
+ printed is now part of the reason. What counts as honoured, ignored or inconclusive is unchanged.
231
+
232
+ ## [0.5.4] - 2026-08-28
233
+
234
+ ### Added
235
+
236
+ - A gate that holds every registered hook to one rule: a hook may not add anything to what the host
237
+ runs. A neighbouring package spent a whole plan undoing the opposite habit — its `PreToolUse` hook
238
+ wrapped the agent's command in a pipe, and because a host permission rule is matched against the
239
+ beginning of the final string and is not applied at all once that string carries a substitution or
240
+ a compound operator, every command it touched became unresolvable by the operator's own rules.
241
+ This package reported those windows on 2026-08-15 while carrying none of the cause. The gate reads
242
+ both behaviour and sources: a hook may return only the keys that refuse or speak
243
+ (`permissionDecision` of `deny` or `ask`, `permissionDecisionReason`, `additionalContext`), and no
244
+ hook source may mention a key that replaces what the host is about to run. `permissionDecision:
245
+ "allow"` is refused as well — the host stopped honouring it between 2.1.119 and 2.1.231, and while
246
+ it worked it was a blanket auto-approve over the operator's own deny rules.
247
+
248
+ - `doctor` and `install` now say whether this host still honours a hook refusal. Every guard in this
249
+ package refuses work with `permissionDecision: "deny"`, and the sibling key of that same object
250
+ died quietly — a neighbouring package established that `"allow"` stopped being honoured somewhere
251
+ between host 2.1.119 and 2.1.231, with no error printed on the way. Nothing here would have
252
+ noticed `deny` going the same way, because the suite checks that a hook RETURNED a refusal, never
253
+ that the host APPLIED it. The record is bound to a host version, since that is what changes
254
+ underneath an installation, and reports five states: never probed, probed on another version,
255
+ refusals ignored (which names the guards it makes inert and exits non-zero), verified, and a host
256
+ whose version cannot be read.
257
+
258
+ - `codex-bridge doctor --probe-contract` performs that measurement on a live host, and every message
259
+ that a probe could change now names it. The probe builds a throwaway rig outside the repository,
260
+ registers one temporary hook there that refuses one marker command, runs the real host inside it
261
+ and judges by whether the marker file appeared. Three conditions were bought by a measurement that
262
+ first reported a working host as dead: only the tool under test is allowed (`--allowedTools Bash`),
263
+ because the nested session otherwise sidesteps the refusal with `Write` and writes its own report
264
+ of being blocked into the marker file; the verdict comes from what executed, never from what the
265
+ model said about it; and settings are read from the project alone, or the operator's own hooks get
266
+ measured instead. A crash, a timeout or a hook that never fired records nothing and exits 2 — an
267
+ absent marker is only evidence when the host survived to the end, and a probe that announced a
268
+ contract as verified without measuring it would be the very silence this record exists to break.
269
+
270
+ ### Fixed
271
+
272
+ - A run now reads the operator's configuration instead of the copy shipped inside the package. The
273
+ installer seeds `config.json` into `~/.lyupro/.codex-bridge/`, the CLI read it from there, and the
274
+ runner built its own path from the module it happened to live in — so `codex-bridge run` opened the
275
+ package's seed, in which no profile is ever configured. A model and a reasoning depth pinned by the
276
+ operator on 5 August reached no run at all: every run silently used defaults, `env.json` recorded
277
+ `"models": {}`, `meta.json` recorded no model because no `-m` was ever passed, and `doctor` kept
278
+ confirming the setting because it resolved the path correctly. Three releases shipped that way, and
279
+ it surfaced only when a service refused a reasoning level and the command line in the run folder
280
+ turned out to name no model. Installed from npm, the file the runner read also sat inside
281
+ `node_modules` and was replaced by every update, contradicting the documented promise that
282
+ configured profiles survive one. There is now one resolver of the host home for both halves of the
283
+ repository, the printed state names the file together with where that path came from, and an
284
+ end-to-end test fails if a configured profile stops reaching the assembled Codex command line — the
285
+ existing unit tests injected the profile as an argument and could not see the file at all.
286
+
287
+ - Every reply now names the worker that ran it: `Model: <model> at <depth> effort (<where the depth
288
+ came from>)`, beside the log link. `worker.json` and `meta.json` carry the same profile, including
289
+ whether the model came from the configuration or was left to Codex, and whether the depth came
290
+ from the request, the configuration or the fallback. A dispatcher reads these rows and nothing
291
+ else, and for three releases none of them named a model at all — which is why a run on a model
292
+ nobody ordered could go unnoticed. A depth silently served by the fallback is the same silence one
293
+ level down, so it is reported the same way. Runs recorded before the field simply keep their old
294
+ replies.
295
+
296
+ - `minimal` is no longer accepted as a reasoning effort. It passed validation and was then rejected
297
+ by the model itself (`Unsupported value: 'minimal' is not supported with the … model`), so an order
298
+ died four seconds after launch on quota already spent. The allowed set is now exactly what the
299
+ service enumerates: `none`, `low`, `medium`, `high`, `xhigh`, `max`. A validator that admits a
300
+ known-dead value is worse than no validator: it moves the refusal past the point where refusing is
301
+ free.
302
+
303
+ ## [0.5.3] - 2026-08-23
304
+
305
+ ### Fixed
306
+
307
+ - The worktree lock knew writing by tool name, and a shell writes anyway. It denies edits inside a
308
+ repository held by a live `codex-build` run, but was registered only for
309
+ `Write|Edit|MultiEdit|NotebookEdit`; on 2026-08-16 a `python - <<PY` heredoc inside `Bash` walked
310
+ past it and appended to `CHANGELOG.md`, and the run reported `FAIL — out-of-scope changes (1)`
311
+ for work it had finished correctly. Two guards now cover the gap from both sides. The lock's
312
+ matcher takes in shell tools as well, and `shell-write-intent.mjs` refuses the obvious forms —
313
+ redirection, `tee`, `sed -i`, `cp`, `mv`, `rm`, `touch`, `truncate`, a heredoc fed to an
314
+ interpreter — before they run. Behind it, `worktree-witness.mjs` compares the tree after every
315
+ shell command against the run's recorded snapshot, drops everything inside the run's declared
316
+ scope, and names whatever is left: a run may only write inside its scope, so the rest is another
317
+ hand. The recogniser states in its own header that its list is not complete and cannot become one
318
+ — that is precisely why the witness stands behind it.
319
+
320
+ - The recogniser knew `sed` and did not know `sed.exe`. Its interpreter pattern had always allowed
321
+ the suffix — `python.exe` opens a heredoc like `python` does — but the command list compared bare
322
+ basenames, so the spelling this platform hands over walked past the guard while the identical
323
+ command without the suffix was refused. Found accepting a delegated run of 2026-08-17 that had
324
+ recorded the gap as intended behaviour: a test asserting `sed.exe -i` is not a write. A test that
325
+ states a defect is true keeps it, so the recogniser now strips the suffix and the case asserts the
326
+ refusal.
327
+
328
+ - Delegated read-only runs could not execute a single command. codex-cli 0.149.0 on Windows builds
329
+ its sandbox from `windows.sandbox` — `elevated` or `unelevated` — and the unelevated token refuses
330
+ CreateProcess outright: every command a run tried came back as
331
+ `Rejected("… rejected: blocked by policy")` before the process existed. The setting lives in the
332
+ operator's `config.toml`, which `--ignore-user-config` drops on purpose, so on 2026-08-23 every
333
+ `codex-scout` and `codex-review` run reported honestly that it could not read anything and was
334
+ then failed by its own quality gate for having no code references. `codex-build` kept working, and
335
+ that looked like "read-only is broken" — but it is the one agent that does not pass
336
+ `--ignore-user-config`, so it inherited the key from a line in a personal file the package does
337
+ not own; with that flag its `workspace-write` runs are refused just as flatly. Four probes on the
338
+ installed CLI, differing by this flag alone, separate the two. All three agents now pass
339
+ `-c windows.sandbox=elevated` on Windows, so isolation from the operator's config stays intact and
340
+ the sandbox each agent is entitled to is the one it actually gets. The value carries no quotes
341
+ because the command line goes through cmd.exe, where a double quote is refused before launch.
342
+
343
+ - A hook fix could not reach a host that already had the hook. Installation identifies our entry by
344
+ its command and ignores which matcher group it sits under — deliberately, because the matcher
345
+ grows whenever a host spelling is added and a lookup by matcher would register a duplicate and
346
+ orphan the old entry at uninstall — but nothing afterwards moved the entry to the matcher the
347
+ package declares now. A host installed before 2026-08-17 kept `Write|Edit|MultiEdit|NotebookEdit`
348
+ for the worktree lock while the package had been saying `…|Bash|PowerShell` for days:
349
+ `update --force` reported success and rewrote nothing, so the guard never saw a shell command and
350
+ two honest delegated runs were failed for a file written through their window. Merge now moves the
351
+ entry under the declared matcher, drops the group it emptied, leaves a group holding someone
352
+ else's hooks exactly as it was, and still cannot produce a second registration of our command for
353
+ one event.
354
+
355
+ - `doctor` stated the matcher this package expects as though it had read it. Its hook line was built
356
+ from the registry, so a host still registered under an older matcher was reported as healthy with
357
+ the new matcher's text — on 2026-08-17 it printed `…|Bash|PowerShell` over a `settings.json` that
358
+ said `Write|Edit|MultiEdit|NotebookEdit`, and the operator had no way to see that the worktree
359
+ lock could not see a shell command at all. Same shape as the 2026-08-10 defect in this file:
360
+ a check that compares the package with itself cannot fail. The line now prints the matcher the
361
+ host recorded, and a difference from the declared one is a `warn` naming both values and
362
+ `codex-bridge update --force`.
363
+
364
+ - The worktree lock refused four kinds of work it cannot protect. A miss by this guard is
365
+ survivable — the witness names the fact afterwards — but a false refusal has no backstop and
366
+ simply stops the orchestrator. An ignored path was refused, which made the plans and checklists a
367
+ live run is documented in unwritable, although a run is only ever failed for what
368
+ `git status` reports; an unexpanded shell variable was resolved against the working directory and
369
+ refused as a path inside the repository; a backtick in the TEXT of a heredoc was refused as a
370
+ path; and a comparison operator in code (`node -e "i>0?'yes':'no'"`) was read as a redirection.
371
+ The lock now passes a path git proves is ignored — and only that, so a missing repository or a
372
+ missing git keeps the refusal, while `.git` stays refused whatever git answers — a redirect
373
+ target wins over document text (the 2026-08-16 python heredoc is still refused), and a candidate
374
+ opening with `$`, `%` or a backtick, or holding a character no file name may contain, is not
375
+ reported as a target.
376
+
377
+ - Every number this package advertises about itself was wrong. The npm description said "5 guards,
378
+ 604 tests" — a count three releases old, next to a registry that had grown a sixth hook the same
379
+ week — README said 705 while the suite ran 757, and CLAUDE.md said five guard hooks. Each was
380
+ caught by eye during a release, which is how the one in `package.json` survived three of them: a
381
+ number written in prose has no reader that can fail. The whole-suite run now compares the size
382
+ README claims against what the run reported and exits non-zero naming both, and a test holds the
383
+ guard count in README, CLAUDE.md and the package description against `HOOK_DEFINITIONS`. Deleting
384
+ the sentence fails the same way an outdated one does; a single-file run reports nothing to
385
+ compare and stays silent.
386
+
387
+ ## [0.5.2] - 2026-08-16
388
+
389
+ ### Fixed
390
+
391
+ - The guard punished the very refusal the package demands. A dispatcher whose command the host
392
+ denies is told by its own prompt to answer `FAIL`, name its order id and name `codex-bridge
393
+ install`; the guard knew only `RUN=` and `ATTACH=`, so that honest answer read as "no run
394
+ exists" and was blocked three times before the exhausted try budget let it through. A probe on
395
+ 2026-08-16 with `Bash(codex-bridge:*)` denied spent 31.5k tokens on an answer worth ten, and the
396
+ counter recorded `{"state": 3}` against it. A host refusal is now a recognised shape beside the
397
+ two run lines: a reply that declares failure, names its order id and names the remedy passes on
398
+ the first try, before any check that looks for a folder on disk. A reply that declares failure and
399
+ names the remedy but omits the order id is blocked with that exact part named — the prompt asked
400
+ for it in prose, and prose is a request a model can silently skip.
401
+
402
+ - No Claude Code host had registered the three dispatchers since 2026-08-10. The stop-guidance
403
+ sentence added that day — `` before `TaskStop`: TaskStop removes the wrapper … `` — was
404
+ substituted into the `description:` line of each agent's frontmatter, and an unquoted YAML scalar
405
+ cannot contain a colon followed by a space: the frontmatter did not parse, so the agents did not
406
+ exist as far as the host was concerned. `doctor` reported every file present the whole time,
407
+ because it counted files instead of reading them. Installation now re-emits frontmatter as valid
408
+ YAML — any value a reader would not take as a plain scalar is quoted, the sentence survives
409
+ verbatim — `doctor` parses each installed definition and checks its `name`, and the suite parses
410
+ everything the installer would write.
411
+
412
+ - A run ordered under another run's order id is no longer answered with that run's verdict. On
413
+ 2026-08-15 a build ordered as `plan42-run3` carried the id of `plan42-run2`, and the runner
414
+ printed run2's `OK`, run2's file list and run2's suite over a worktree where nothing had been
415
+ done — only a manual `git status` stopped a commit of work that did not exist. An order id names
416
+ one task: when the id already belongs to a run whose task text differs, the runner now refuses
417
+ with exit code `2` before printing anything, naming the folder that owns the id and the two
418
+ remedies (a new `--order-id`, or `--continue`). Repeats of the same task, continuations, and runs
419
+ recorded before the `task_hash` field attach exactly as before.
420
+ - The same collision is now refused one step earlier and checked one step later. `order-gate` denies
421
+ the dispatcher call itself when the ordered id already belongs to a run with a different task —
422
+ before any quota is spent — and `reply-guard` blocks a reply whose run records a different
423
+ `order_id` than the one the orchestrator ordered, read from the dispatcher's own transcript. Both
424
+ read the single definition of "the same task" the runner uses; a second copy of that rule would
425
+ drift silently, which is the failure this change exists to end. Every uncertain disk state — an
426
+ unreadable task file, a missing runs directory, a run folder written before its `status.json` —
427
+ passes, because a guard nobody can predict is a guard everyone works around.
428
+ - Delegating to a dispatcher no longer stops on a permission prompt. The shipped agent definitions
429
+ started a run by absolute path — `node "<installed dir>/run-codex.mjs" --agent … \ …` — and a host
430
+ matches permission rules against the beginning of a command line, so a rule written for this
431
+ package could never match it. The only way to start a run is now the package command,
432
+ `codex-bridge run`, on a single line: no interpreter, no path, no line continuation. The task
433
+ statement travels as `--task-file`, because a heredoc forces the invocation back into the
434
+ multi-line form that no rule can cover; stdin remains supported for the direct call, and giving
435
+ both channels is a refusal rather than a silent precedence.
436
+ - Installed hooks could not run at all. The installer reshaped the source tree — hooks to one
437
+ directory, their modules to another — so every hook's relative import was correct in the clone and
438
+ false on the host, and all five guards died on `ERR_MODULE_NOT_FOUND` from the day the package
439
+ moved to its branded folder. The suite stayed green because it exercises the clone, and `doctor`
440
+ called a hook healthy on the strength of a line in `settings.json` without ever starting the file.
441
+ The source now contains a literal image of the installed home, `src/home/`, and installation
442
+ copies it one to one.
443
+ - The reply guard no longer sends a refused dispatcher down the one road the host cannot allow.
444
+ Seven of its block texts said "Run run-codex.mjs and return its stdout verbatim" — the sentence
445
+ removed from every prompt in this release, because a command line naming a file can match no
446
+ permission rule. They were the worst place for it to survive: a dispatcher reads them at the one
447
+ moment it is looking for what to do next, having just had its answer rejected. All seven name
448
+ `codex-bridge run`. The invariant behind them was the actual defect and is now stated as a rule
449
+ rather than as a shape: no line of code in a hook may name the runner file, whole-line comments
450
+ excepted. The earlier expression-bound check could not see the text a session is shown when its
451
+ turn is ended, which is where the same sentence would have survived a fourth cleanup.
452
+ - `doctor` no longer calls a host healthy when an installed agent definition is not this package's.
453
+ Drift was advisory — a message, exit code 0 — while an unreadable definition was already fatal. A
454
+ checklist run planted the pre-0.5.2 invocation, a file path instead of the package command, in an
455
+ installed agent: `doctor` printed the drift and still exited 0, so a script or a CI step would
456
+ have called that host healthy while every delegation on it stopped at a permission prompt. Any
457
+ definition that differs from the package now exits 1 and names `codex-bridge update --force`.
458
+
459
+ ### Added
460
+
461
+ - `codex-bridge run` — the public entry point for starting a delegated run, with the same flags,
462
+ stdout and exit codes as the historical file invocation.
463
+ - Both lines that identify a run to its caller carry the order they belong to:
464
+ `RUN=<path> order-id=<id>` and `ATTACH=<path> order-id=<id> started=<time>`. A reply naming a run
465
+ cannot be checked against the order that was placed unless the id travels with the folder.
466
+ - Two structural guards that make the above regressions loud instead of silent: the install plan
467
+ must stay isomorphic to the shipped home image, and every registered hook must actually start as
468
+ a child process with its imports resolved.
469
+ - `doctor` compares the version answering a hook against the installed copy, verifies the installed
470
+ agent definitions against the repository, and downgrades to a warning instead of reporting code it
471
+ never ran as healthy.
472
+
473
+ ## [0.5.1] - 2026-08-12
474
+
475
+ ### Fixed
476
+
477
+ - Installing or uninstalling no longer fails over a rules-registry lock that is busy for one more
478
+ millisecond. Windows answers a taken lock with three codes, and only one of them is `EEXIST`: a
479
+ lock file whose delete is still pending comes back `EPERM`, and a file another process still
480
+ holds comes back `EBUSY` — the acquisition loop treated both as fatal instead of waiting. All
481
+ three now mean "not yours yet", and the decision lives in one exported check, `isLockTaken()`.
482
+
483
+ ### Changed
484
+
485
+ - The README's first screen names the problem the package solves, keeps the install command inside
486
+ the first 30 lines, adds a second badge row, and shows `doctor` output from a healthy host in
487
+ place of a screenshot.
488
+ - The reference documentation under `docs/` is English-only now (`overview.ru.md` became
489
+ `overview.md`), and the working folders — plans, checklists and audits — left the repository and
490
+ its history: a fresh clone contains the five reference documents and nothing else.
491
+
492
+ ## [0.5.0] - 2026-08-11
493
+
494
+ ### Added
495
+
496
+ - A fifth host guard now catches `TaskStop` while a live Codex run is still writing to the
497
+ repository, names the run and points to `codex-bridge stop <run>` before allowing a repeated
498
+ request. `doctor` and `projects` also surface active runs, so stopping a dispatcher wrapper is
499
+ no longer mistaken for stopping its detached worker.
500
+ - `codex-bridge hook <name>` gives Claude Code a stable way to invoke each installed guard, and
501
+ `meta.json` now records the package version that produced a run. The usage report can therefore
502
+ separate historical runs made outside this runner from runs that should satisfy its sandbox
503
+ contract.
504
+
505
+ ### Changed
506
+
507
+ - **Breaking:** the installed package layout now uses `agents/codex-bridge/` instead of
508
+ `agents/codex/`, `commands/codex-bridge/` instead of `commands/codex/`, and
509
+ `~/.lyupro/.codex-bridge/lib/` for the runner and guards. Slash commands are now
510
+ `/codex-bridge:usage` and `/codex-bridge:env`, and hooks in `settings.json` use
511
+ `codex-bridge hook <name>` when the matching package version is available. Run
512
+ `codex-bridge update` to migrate an existing installation and retire the old layout.
513
+ - Runs started without an explicit `--slug` now derive a readable folder name from the required
514
+ order id instead of grouping unrelated work under the dispatcher name. Existing run folders
515
+ remain discoverable by their order id and task fingerprint.
516
+ - The README now leads with the package's guarantees, verification and update guidance, and
517
+ limitations before the command reference, and the repository now publishes a security policy
518
+ and private vulnerability-reporting route.
519
+
520
+ ### Fixed
521
+
522
+ - Guards invoked through a global-install symlink no longer exit successfully without running;
523
+ the direct-invocation check now compares resolved files, so a silent guard cannot allow every
524
+ operation while `doctor` reports it healthy. Hook registration also chooses the short command
525
+ form only when the command on `PATH` is this package's exact version, rather than merely present.
526
+ - Retiring the previous layout no longer removes the shared Claude Code `agents/` and `commands/`
527
+ directories. Seeded operator files are moved only after byte verification, and an interrupted
528
+ migration is retried by the next `update` even when the new files are already current.
529
+ - A continuation grant that loses its `--continue` flag is now refused before attachment instead
530
+ of replaying an older run's verdict as though new work ran. Attachment output also states
531
+ explicitly that no new work was started.
532
+ - The test suite now isolates both installation roots, preventing tests from writing fixtures or
533
+ seeded configuration into the operator's real `~/.lyupro/.codex-bridge/` directory. Migration
534
+ dry runs also name the hook event and matcher instead of printing `undefined`.
535
+
536
+ ## [0.4.0] - 2026-08-09
537
+
538
+ ### Added
539
+
540
+ - Scope patterns are checked before Codex starts. A pattern that cannot match anything — an
541
+ absolute or drive-qualified path, backslash separators, a `..` segment, or a spelling no
542
+ repository file answers to — is refused with no run folder and no quota spent, for all three
543
+ dispatchers. The file list comes from `git ls-files --cached --others --exclude-standard`, so
544
+ ignored output does not count as repository content. A file the run is meant to create is
545
+ declared with `--scope-new`, accepted only by `codex-build` and recorded in `worker.json`.
546
+ - `codex-bridge permissions` reports, adds, and removes the optional shell permission rules for
547
+ both CLI names and the clone entry point. The exact allow and deny strings merge into
548
+ `settings.json`, preserve foreign rules, and are also removed by `uninstall`; `doctor` reports
549
+ an absent or partial set as an optional warning.
550
+
551
+ ### Changed
552
+
553
+ - `codex-bridge sweep` is now `codex-bridge unlock`. The default scope is the current repository;
554
+ a bare project name remains addressable, and `--all` is now required for the whole store. The
555
+ report shows age, silence, and `alive` / `dead` / `foreign` / `unverified` identity verdicts;
556
+ confirmed-alive records are refused with `codex-bridge stop <run>` instead of being closed.
557
+
558
+ ### Fixed
559
+
560
+ - Run records now verify that a pid still belongs to the process that started the run, using the
561
+ process start time and a fresh heartbeat before falling back to an OS probe. A reused or
562
+ unverified pid is never signalled by `stop`; `codex-bridge unlock` remains the non-killing way to
563
+ close a record whose process identity cannot be confirmed.
564
+ - JSON files written by PowerShell with a UTF-8 BOM are now read consistently by the CLI and
565
+ installed hooks, and one command run creates at most one settings backup without deleting older
566
+ backups.
567
+ - A run a guard refused before Codex ever started no longer costs the next attempt a round trip. Such a folder now closes as `aborted_pre_start` instead of the same `failed` a twenty-minute paid run gets, and the chain gate, the continuation limit and the tree baseline all skip it: the same order id starts as a first run, silently, with no `continue:` grant. Reported from a parallel `codex-build` launch — the second run was correctly refused with "two writing runs in one tree are prohibited", and its one-millisecond folder then made the retry demand a grant for work that never happened. Folders written before this release are recognised by what `meta.json` records — `exit`, `session_id`, `events_bytes`, `stderr_bytes`, `tokens_reported` — rather than by whether `events.jsonl` is still on disk, because retention deletes that file from paid runs too. The folder itself is kept: it is the audit trail of who was refused and why.
568
+ - `chainBaseline()` takes the first run of the chain that actually started, so a pre-start folder sorting first no longer leaves the next pass without the `state-before.txt` its work is measured against.
569
+ - `uninstall --dry-run` names each hook by its own matcher. Three of the four hooks are PreToolUse,
570
+ and a lookup by event alone reported `Agent|Task` for the worktree lock and the prune guard too —
571
+ in the one line an operator reads to see what a removal is about to touch. The removal itself was
572
+ never affected: hooks are found by command, not by matcher.
573
+ - The order gate's refusal now shows the line it found. A caller who wrote
574
+ `scope (you may create/modify ONLY these):` was told scope was missing, re-read their own prompt,
575
+ saw it written there, and had nothing to act on; an autonomous run rewrites the same text forever.
576
+ The refusal names the candidate line under the input it belongs to and says why it was not
577
+ accepted — a qualifier between the label and the colon, a value that is still a placeholder, or no
578
+ separator at all — and stays silent when no candidate exists, because then "the input is missing"
579
+ is the truth. The parser accepts exactly what it accepted before: deriving scope from prose would
580
+ give away the only guarantee that a run cannot touch files nobody listed.
581
+
582
+ ## [0.3.1] - 2026-08-07
583
+
584
+ ### Added
585
+
586
+ - The CLI answers to `codexb` as well as `codex-bridge`, and README now documents the global install that puts both names on `PATH`. Every spelling that can reach deletion comes from one frozen list, and the prune guard builds its matcher from that list rather than repeating the names — two independent lists drift silently, which is exactly how the installer and test hook lists had already drifted. The full name stays primary: the docs, checklists and three agent prompts are written with it.
587
+ - Every run carries the rules it is judged by. A seeded `conventions.md` beside `run-config.json` and an optional `.codex-conventions.md` in the repository being worked on are pasted into `task.md` verbatim, under one `## Conventions` section. Either file missing is not an error, and a blank one produces no section at all — rules handed over as an empty heading read as rules ignored. Until now project rules reached a run only when the orchestrator remembered to write "read CLAUDE.md" into the task, and six acceptance passes in a row caught the same class of violation: the rule existed, applied, and had not travelled. Pasting rather than referencing is the point — "read file X" is hope, and a run is free not to.
588
+ - `doctor` opens with the path of the package copy answering, marked `clone` or `installed package`, and reports whether the host conventions file was found, warning when it exists but is empty. A global install puts a second copy of the package beside any clone, and `update` copies host files from whichever copy launched it — so two diagnoses of the same host were previously indistinguishable.
589
+
590
+ ### Fixed
591
+
592
+ - `codex-bridge projects` printed the `last run` column on two clocks. Folder names are written in local time and `meta.finished_at` is an ISO stamp in UTC, and each reached the column through its own branch, so a run whose folder says `144243` was listed as `12:46` — the operator's own zone offset, indistinguishable from a different run. Which run is newest was never affected, only how it printed.
593
+
594
+ ## [0.3.0] - 2026-08-07
595
+
596
+ ### Added
597
+
598
+ - `codex-bridge sweep [<project>]` closes running records whose runner pid is dead, across the whole store or one project. It changes state but deletes no run folder or transport, never touches a live pid, and lists every stalled-but-live run with the exact `codex-bridge stop <run>` line that ends it. A dead record can therefore be cleared without paying for another run, while a live worker remains the sole writer until `stop` kills it first.
599
+ - Every new run folder carries a `heartbeat` file, and `status.json` carries `stdio_drained`. The heartbeat records movement from Codex output and the still-running child rather than a timer that would keep an orphan alive; `stdio_drained` says whether stdout/stderr closed normally or the bounded drain grace had to end the run.
600
+
601
+ ### Changed
602
+
603
+ - Hook liveness now requires `state: running`, a live runner pid and a heartbeat no older than five minutes. A stalled run therefore stops holding the worktree lock instead of holding it until the deadline; the refusal that remains — always about a working run — says how long ago it last made progress and prints `codex-bridge stop <run>` for taking the repository back early. Record cleanup and the second-writing-run check still judge by pid alone, so a stale but live run is released to the operator, not closed underneath its worker.
604
+ - The worker closes a run after `exit` with a bounded 30-second grace for stdio drain instead of waiting for `close`, which a grandchild can hold open forever. Healthy runs keep the event tail that carries their result and record `stdio_drained: true`; only the fallback records `false`.
605
+ - The three dispatcher prompts now open with one shared no-self-execution block rendered through `{{CODEX_NO_SELF_EXECUTION}}`. If `run-codex.mjs` does not start a run, the dispatcher must return `FAIL` and stop, and a reply with no run folder is blocked by the guard. The installed prompts change only when the host runs `codex-bridge update`; a host that does not update keeps the old prompts.
606
+
607
+ ### Fixed
608
+
609
+ - A dispatcher that cannot start Codex can no longer read the code, write the files or fix the tests itself and claim a result. The guard also rejects a made-up reply with no `RUN=` or `ATTACH=` behind it: no run, no result.
610
+
611
+ ## [0.2.0] - 2026-08-06
612
+
613
+ ### Added
614
+
615
+ - `codex-bridge projects` gives the run store a way to look at itself: one line per project — runs, weight, tokens spent, how many are running right now, when the last one started — and one line per run under a project name, plus `--json` for scripts. The store had grown to 12 projects, 152 runs and 80 MB with no command that could answer "what has Codex been working on"; the only way in was a file manager. The count of live runs is computed by the same module the reply guard and the worktree lock use, because a second detector of a live run sitting next to the first would drift from it silently.
616
+ - `codex-bridge prune` reclaims that space, and it is built to be hard to fire by accident: without `-f` it prints a plan and deletes nothing, `-f` still asks before each step, and with no terminal it refuses outright — deletion is an operator action, and the bypass flag that would undo this is deliberately absent. A gentle prune removes transport only (event streams, stderr, the archived `raw.log` of older runs) and leaves accounting, reports and worktree snapshots in place, so a task can still be continued and still be read after its megabytes are gone; `--purge` is what takes a folder whole, and it is refused together with `--all-projects`, because deleting a dozen projects with one command has no second line of defence. A live run is never a target — it is recognised by its process, not by a status file, since the file that would be deleted is the verdict being written right now.
617
+ - Run transport is dropped automatically from runs older than 30 days when a new run starts, and the dispatcher's reply says how much was freed: a rare event that goes unexplained sends someone looking for a fault. Automatic cleanup can only shed weight — the code to remove `meta.json` or a folder does not exist on that path at all, so it cannot be switched on by mistake — and `retention` in `run-config.json` changes the age or turns it off. Both `install` and `doctor` say out loud that it is on, because an installer that quietly starts deleting files is a surprise, not a convenience.
618
+ - A fourth hook refuses `codex-bridge prune` in an agent's shell command before the deleting process exists. Mentioning the command is not calling it: heredoc bodies and quoted text are stripped before the check, after the guard rejected the very commit that introduced it over a command name in the message. `git prune` and `npm prune` are left alone — a guard that breaks unrelated work is a guard that gets removed, and then it protects nothing.
619
+ - `codex-bridge read <run>` renders a run's events as text on demand. Readability is now something the package produces when asked rather than a file it keeps, and a rendering that is not stored cannot disagree with the facts.
620
+
621
+ ### Changed
622
+
623
+ - Runs go through `codex exec --json`, and `events.jsonl` is the single source of the quota signal, the token count, the session id and the failure reason. The stream is written by the CLI, not by the model, so a quotation out of a source file can no longer reach it. `tokens` is input plus output with the CLI's whole `usage` object beside it, and `session_id` comes from `thread.started` instead of a line matched in a log.
624
+ - The reason for a `FAIL` is taken from the run's own error event rather than from `stderr.log`. A live probe showed `stderr.log` is not empty even for a clean run — 488 bytes of execpolicy refusals, repository paths included — which made the reason a random blocked call instead of the real one. The transport branch keeps the reason the CLI itself gave with its status code: a `LIMIT` whose reason does not mention the quota is the same class of defect.
625
+ - A run that never began is one with no events *and* no stderr. Stderr with no events is a CLI that did not understand a flag — a different diagnosis, calling for a different action, and the two no longer collapse into one.
626
+ - `log_bytes` in `meta.json` is replaced by `events_bytes` and `stderr_bytes`.
627
+
628
+ ### Removed
629
+
630
+ - `raw.log` is gone. Under `--json` it would have been a byte-for-byte copy of `events.jsonl` — a second source of truth with no role of its own — and the package already declared it ephemeral and safe to delete while resting the "abandoned at start" verdict on it. An artifact one is allowed to remove cannot be the ground of a verdict.
631
+ - The text detector of the quota signal is removed entirely rather than kept as a fallback. While the regular expressions live, so does the class of defect they caused.
632
+
633
+ ### Fixed
634
+
635
+ - `LIMIT` is set only by an error event from the CLI carrying `status: 429` or an error type naming the limit. A `codex-build` run that had finished its work grepped the package's own test fixtures, printed a line where `ERROR` and `rate limit` stand together, and was closed as quota-exhausted — the work was complete and the suite was green, and the verdict threw it away.
636
+ - A run killed by its time budget says so. It records `stopped_on_deadline` and answers `FAIL — run stopped on its deadline`, instead of inventing a reason out of whatever text was nearest.
637
+ - A log file that cannot be written no longer leaves Codex orphaned. An unhandled stream error used to kill the worker while the CLI kept burning quota with nothing left to close it; now either stream failing takes down the process tree, so the run dies with its process rather than instead of it.
638
+ - A run that exited honestly is no longer marked as killed by its deadline. The timer judged by `close`, which a grandchild process can hold open long after the run is over; it now watches `exit`.
639
+ - A verdict is never shortened to fit the terminal. In a narrow window `running` was printed as `…nning`, which reads as a damaged run rather than one still in flight; a verdict is one word from a known set, like a number or a date, and the run name is what a narrow table gives up instead.
640
+ - The CLI's own warnings are no longer read as the model's complaint about the order. `codex-cli` announces a deprecated configuration key through the same event shape the model uses for a content error, before the turn even opens — so a failed run on such a host would have answered "`[features].codex_hooks` is deprecated" when asked why it failed. Only events after the first `turn.started` are the model's; a complaint about the order cannot predate the model's first thought.
641
+
642
+ ## [0.1.4] - 2026-08-05
643
+
644
+ ### Added
645
+
646
+ - A continuation has to prove the orchestrator ordered it. `--continue` is refused unless the task text carries a `continue:` grant naming the run being continued and why, and the refusal costs nothing: no run folder is created and Codex is never started. The grant is single-use by construction rather than by a counter — continuing the last run of a chain appends a later one, so the same line stops matching by itself. Until this release a dispatcher could decide on a second pass alone: one did, spent 75 691 tokens of the Codex subscription and created a module the order never asked for.
647
+ - A reply that stays silent about a live `codex-build` run of the same project is blocked. The guard reads the project's runs folder from disk instead of trusting the paths the reply chose to mention, so a second run can no longer be left working in the background while the verdict of the first is handed back. Runs of `codex-scout` and `codex-review` are not counted: they hold a read-only sandbox, and blocking a reply over one would spend the guard's budget on a run that touches nothing.
648
+ - A `PreToolUse` lock refuses file edits inside a repository held by a live `codex-build` run. Knowing that a run is live is not the same as being unable to disturb it — edits made during that window land in the runner's before/after snapshot and fail an honest run for work it never did. An edit made through `Bash` still walks past the lock; parsing a command line is not something a guard can do reliably.
649
+
650
+ ### Fixed
651
+
652
+ - `doctor` names the file each hook was registered for. With two hooks on one event it matched the installation record by event alone and reported the worktree lock's matcher as pointing at `order-gate.mjs` — the one line where an operator can check what is installed, showing the wrong file.
653
+ - Hook presence is decided by this package's own command rather than by the matcher it currently sits under. The matcher is generated from the list of host tool names and grows whenever a spelling is added, so a host installed under the previous matcher would collect a duplicate hook on update and keep a hook pointing at a deleted file after uninstall.
654
+
655
+ ## [0.1.3] - 2026-08-05
656
+
657
+ ### Added
658
+
659
+ - Dispatcher calls are refused before a subagent exists. A `PreToolUse` gate rejects a `codex-*` call whose task text carries no order label — and, for `codex-build`, no scope — or whose value is still a template placeholder. Until this release the missing input was discovered by the runner, after the subagent had been created and paid for out of the Claude subscription.
660
+ - `codex-build` declares the outcome of its own run in a required `outcome` field. A run that could not do the work reports `fail`, and the verdict goes red with the reason even when the worktree is clean and verification is green — the shape that used to answer `OK`, because an empty delta is a legitimate outcome the runner cannot tell apart from a failure. Whether a run owed that declaration is read from the response schema it was handed, so runs recorded before this release keep the verdict of their own day.
661
+
662
+ ### Changed
663
+
664
+ - Every dispatcher's description and prompt lists the inputs it requires, filled in at install time from the same table the gate enforces, and the runner's own refusals name the input, an example value and what to do about it. A caller no longer learns the contract by being refused.
665
+ - A failed `codex-build` reports what it left in the worktree, the way a quota-exhausted run already did. "The work was not done" is not "the tree is clean", and where no snapshot exists the line says so instead of claiming there were no changes.
666
+ - The installation record stores a list of hooks rather than a single hook, so a host can register more than one. Records written by earlier versions are read as a one-element list.
667
+
668
+ ## [0.1.2] - 2026-08-04
669
+
670
+ ### Fixed
671
+
672
+ - A damaged ownership registry no longer breaks the command that reads it. Every command parses the registry before its first write, `doctor` reports the damage as a failed check while still printing the other eight, and `uninstall` finishes removing the package but keeps the shared rules file, because ownership is unknown and disarming another installation is worse than leaving a file behind. Until this release an `uninstall` on a damaged registry stopped after the hook was gone, leaving the host with every package file and no reply guard.
673
+ - Owner updates are written through a temporary file under a lock, so two installers running at once merge instead of overwriting each other. The lock expires: an interrupted run used to leave it behind, and every later install and uninstall then died on it until someone deleted the file by hand.
674
+ - Installation claims its share of the rules file before writing anything. Claiming it last meant a failure at that step left a fully installed host missing from the registry, and the next uninstall elsewhere would delete the rules out from under it.
675
+
676
+ ## [0.1.1] - 2026-08-04
677
+
678
+ ### Fixed
679
+
680
+ - Two runners starting at the same moment in equally named repositories no longer fail on the exclusive writes that assign a runs directory: the runner that loses either race now reads the marker the winner wrote and takes the next candidate, which is the collision the marker was introduced to survive.
681
+ - A repeated order is answered from its newest run instead of the pass before it: while a continuation was in flight, repeating the command returned the previous run's verdict as if it described the current one.
682
+ - Uninstalling one installation no longer takes the Codex rules file away from every other installation on the machine. The file lives in `CODEX_HOME`, which is shared by every host, while ownership was recorded per host and two installations of the same version share a fingerprint — so removing a project-scope or throwaway host silently left the remaining ones running Codex without the execpolicy that forbids destructive repository work. Ownership is now tracked next to the file itself, and the last owner to leave is the one that removes it.
683
+
684
+ ## [0.1.0] - 2026-08-04
685
+
686
+ ### Added
687
+
688
+ - Three dispatcher agents for reconnaissance (`codex-scout`), scoped implementation (`codex-build`), and independent review (`codex-review`), each with a structured response contract.
689
+ - A delegating runner that separates the launcher from a detached worker, preserves run artifacts after a dropped call, and supports configurable artifact roots.
690
+ - A zero-dependency `codex-bridge` CLI with `install`, `uninstall`, `update`, and `doctor`, including user/project scopes, dry runs, force handling, idempotent installs, hook merging with backups, and manifest-based ownership tracking.
691
+ - A reply guard hook that blocks dispatcher replies that do not match the recorded run state or required response shape.
692
+ - Run-chain and busy-tree lock protections: repeated work is linked across runs, an unfinished chain requires explicit `--continue`, and concurrent writing runs for one repository are refused before Codex starts.
693
+ - Scope and coverage validation: implementation changes must stay within the declared scope, report/worktree mismatches fail the run, and reconnaissance reports must account for all requested subquestions.
694
+ - Package-managed Codex execution rules that forbid history-, branch-, index-, and worktree-destructive operations, writes inside `.git`, and other unsafe repository mutations while leaving the operator's existing rules untouched.
695
+ - A 400-line source-file size gate with explicit rationale required for exclusions.
696
+ - Asynchronous run start: the launcher reports the run folder and returns instead of holding the caller for the length of the run, and repeating the same order attaches to the run already in flight rather than paying for a second one.
697
+ - Per-mode time budgets that end a run and its whole process tree at a deadline, plus a `stop` subcommand for closing a run that wedged anyway.
698
+ - Explicit reconnaissance subquestions through a mandatory, repeatable `--question` flag, so coverage is counted against what was ordered rather than against prose the dispatcher rewrote.
699
+ - An `answerLanguage` configuration key that fixes the language of a run's answer instead of leaving it to the model.
700
+
701
+ ### Changed
702
+
703
+ - Run identity now includes an orchestrator-issued mandatory `--order-id`, stored with run state and used alongside the task identity to detect repeats even when a run is renamed.
704
+ - A repeat of an order is now continued at most once, and only behind a run that already carries a verdict, so a dispatcher can no longer restart itself into a chain of paid runs.
705
+ - A run left behind by a dropped call is now closed with a `FAIL` verdict that names what it left in the worktree, instead of staying open with no account of its work.
706
+ - Runs now snapshot both branch and `HEAD` before and after execution; branch changes, detached `HEAD`, and commit drift are treated as failures, with preflight refusal for a subsequent run when an abandoned run left repository state out of sync.
707
+ - English-language prompts and runner/command output are shipped for the three dispatchers and their supporting commands.
708
+ - Installation updates track installed file contents so outdated package files can be refreshed without overwriting hand edits unless `--force` is requested.
709
+
710
+ ### Fixed
711
+
712
+ - Prevented environment and service writes from being misreported as delegated work while keeping them visible in run results.
713
+ - Prevented different repositories with the same directory name from sharing run history.
714
+ - Prevented malformed state files, including files with a byte-order mark, from silently bypassing abandoned-run detection, busy-tree locking, and chain lookup.
715
+ - Fixed diagnostics to report the run folder actually used by the runner and to continue reporting remaining checks when a project marker is unreadable.
716
+ - Fixed reported file paths to keep the spelling on disk instead of the case-folded form used for matching, so a reply names a path that exists on case-sensitive filesystems.