@lyupro/codex-bridge 0.6.0 → 0.6.2

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,578 +1,615 @@
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.0] - 2026-09-07
8
-
9
- ### Added
10
-
11
- - `codex-bridge model` answers which model and reasoning depth each of the three roles runs on, and
12
- where each value comes from — the operator's config file or the package default. The config is one
13
- file per machine, shared by every project on it, and the command says so: changing a model here
14
- changes the executor for every repository this host delegates from.
15
- - `codex-bridge model list` prints the live catalogue, hidden models included and marked as hidden.
16
- A model Codex does not advertise is still a model the operator may choose; omitting it would mean
17
- deciding for them and then explaining why the system has a model the list denies.
18
- - `codex-bridge model set <role> <model> [effort]` and `model unset <role>` write that choice. The
19
- pair is checked against the catalogue entry of that very model at the moment of writing, so
20
- `ultra` — supported by the newest model and rejected by the package's own flat list until now —
21
- became reachable, and a depth the chosen model does not offer is refused with its supported
22
- depths named. Nothing is substituted silently: the command never changes what it was not asked to.
23
- - `codex-bridge model speed <role> <tier>` pins a paid acceleration tier, and `model speed <role>
24
- unset` removes it. The tier is spelled with the identifier the catalogue reports for that model,
25
- never a word held in the package, and the command writes nothing until a second call ends in
26
- `confirm` — the first prints the catalogue's own description of the tier and a link to the page
27
- carrying the credit multipliers. The installer now also places one `ask` rule of its own, so the
28
- host asks the operator before an agent can run that command at all.
29
- - A pinned tier reaches the run: the launcher passes `-c service_tier=<id>` when, and only when, a
30
- role has one pinned.
31
-
32
- ### Changed
33
-
34
- - The depth of a run is no longer validated against a list held in the package. That list had
35
- drifted from the server in both directions — it offered a depth no model accepts and refused one
36
- the newest model supports — so the writing command asks the catalogue and the hot path checks only
37
- the shape of the value. The arbiter at run time is Codex itself, whose refusal is more accurate
38
- than a copy of its data.
39
- - One boundary now owns every write to the run config, and it persists only the edit it was given.
40
- Reading merges the package defaults, and the previous writer saved that merged object, so a single
41
- edit would have frozen every default into the operator's file — worst of all the list of
42
- environment paths, which the package extends as it learns of new ones and which decides whether a
43
- change belongs to the run or to the environment.
44
- - The word `mode` no longer names three different things in this package. The reviewer's flag is
45
- `--changeset`, the prune says which strategy, the table which truncation, and one registry answers
46
- who the three agents are.
47
- - The README states the suite size it actually has.
48
-
49
- ### Fixed
50
-
51
- - Two edits of the config racing each other can no longer lose one of them. Both commands read the
52
- role profiles, waited seconds for the catalogue, then wrote what they had read, and three
53
- concurrent processes all reported success while one edit was missing from the file. The edit
54
- boundary now reads the file itself, hands the fresh value to the caller's transformer, compares
55
- the bytes immediately before publishing, and holds the lock this package already used for the
56
- rules-ownership registry — one definition now serves both writers.
57
- - The worktree lock reads the shell rather than the prose someone wrote into a document. A plain
58
- `cat > file << EOF` handed the whole command, heredoc body included, to the analysis, so the word
59
- `touch` inside an English sentence made the next words look like files being written. The reverse
60
- hole closed with it: the tail after a heredoc's closing marker was not parsed at all.
61
- - A dead run stays dead even when Windows hands its process id to someone else.
62
- - The reader of the Codex user configuration answers about configuration, not about prose inside it.
63
- A multi-line TOML literal could contain a line indistinguishable from an assignment, and it was
64
- read as the setting; an empty argument threw past the module's own error boundary, which exists so
65
- the table renders whatever state the file is in.
66
-
67
- ## [0.5.5] - 2026-08-28
68
-
69
- ### Fixed
70
-
71
- - The write guard now reads quoting, so a redirect character inside a quoted argument is text rather
72
- than an operator. While a delegated run held the repository, `sed -E 's#(A ).*#<redacted>#'` was
73
- refused as a write to a file named `#'`: the scan ran over raw text, so the `>` looked like a
74
- redirect and the token after it looked like its destination. The file already carried a defence
75
- against this class, added after a `node -e` expression was misread the same way, but that defence
76
- filtered candidates holding characters no file name may contain and the one refused here held
77
- none. The guard still fails closed: when quotes cannot be balanced the scan behaves exactly as
78
- before, because answering "no write here" to a line it cannot parse would be worse than the false
79
- refusal being removed.
80
- - The installation summary no longer reads as a bug. Four `PreToolUse` guards printed as
81
- `PreToolUse and PreToolUse and PreToolUse and PreToolUse` because the sentence joined every hook's
82
- event; it now groups by event and counts.
83
- - The installed-file count agrees with `doctor`. The summary counted the Codex rules file among the
84
- files while `doctor` reports it on its own line, so `68` sat next to `67` and read as a lost file.
85
- - `Granted 0 permission rule(s)` no longer reports the good outcome as if it were a failure. When
86
- the set is complete the summary says so; on a host whose settings were edited by hand it says how
87
- many rules are missing and how to restore them, instead of claiming all are in place while the
88
- operator gets permission windows. The already-installed branch reports the set by reading it —
89
- granting there would have restored a deleted rule under the heading "nothing to do".
90
- - A contract probe that refuses to judge now carries the host's own words. `The host exited with
91
- status 1.` was the entire account of a failure on a live host; the last non-empty line the host
92
- printed is now part of the reason. What counts as honoured, ignored or inconclusive is unchanged.
93
-
94
- ## [0.5.4] - 2026-08-28
95
-
96
- ### Added
97
-
98
- - A gate that holds every registered hook to one rule: a hook may not add anything to what the host
99
- runs. A neighbouring package spent a whole plan undoing the opposite habit — its `PreToolUse` hook
100
- wrapped the agent's command in a pipe, and because a host permission rule is matched against the
101
- beginning of the final string and is not applied at all once that string carries a substitution or
102
- a compound operator, every command it touched became unresolvable by the operator's own rules.
103
- This package reported those windows on 2026-08-15 while carrying none of the cause. The gate reads
104
- both behaviour and sources: a hook may return only the keys that refuse or speak
105
- (`permissionDecision` of `deny` or `ask`, `permissionDecisionReason`, `additionalContext`), and no
106
- hook source may mention a key that replaces what the host is about to run. `permissionDecision:
107
- "allow"` is refused as well — the host stopped honouring it between 2.1.119 and 2.1.231, and while
108
- it worked it was a blanket auto-approve over the operator's own deny rules.
109
-
110
- - `doctor` and `install` now say whether this host still honours a hook refusal. Every guard in this
111
- package refuses work with `permissionDecision: "deny"`, and the sibling key of that same object
112
- died quietly — a neighbouring package established that `"allow"` stopped being honoured somewhere
113
- between host 2.1.119 and 2.1.231, with no error printed on the way. Nothing here would have
114
- noticed `deny` going the same way, because the suite checks that a hook RETURNED a refusal, never
115
- that the host APPLIED it. The record is bound to a host version, since that is what changes
116
- underneath an installation, and reports five states: never probed, probed on another version,
117
- refusals ignored (which names the guards it makes inert and exits non-zero), verified, and a host
118
- whose version cannot be read.
119
-
120
- - `codex-bridge doctor --probe-contract` performs that measurement on a live host, and every message
121
- that a probe could change now names it. The probe builds a throwaway rig outside the repository,
122
- registers one temporary hook there that refuses one marker command, runs the real host inside it
123
- and judges by whether the marker file appeared. Three conditions were bought by a measurement that
124
- first reported a working host as dead: only the tool under test is allowed (`--allowedTools Bash`),
125
- because the nested session otherwise sidesteps the refusal with `Write` and writes its own report
126
- of being blocked into the marker file; the verdict comes from what executed, never from what the
127
- model said about it; and settings are read from the project alone, or the operator's own hooks get
128
- measured instead. A crash, a timeout or a hook that never fired records nothing and exits 2 — an
129
- absent marker is only evidence when the host survived to the end, and a probe that announced a
130
- contract as verified without measuring it would be the very silence this record exists to break.
131
-
132
- ### Fixed
133
-
134
- - A run now reads the operator's configuration instead of the copy shipped inside the package. The
135
- installer seeds `config.json` into `~/.lyupro/.codex-bridge/`, the CLI read it from there, and the
136
- runner built its own path from the module it happened to live in — so `codex-bridge run` opened the
137
- package's seed, in which no profile is ever configured. A model and a reasoning depth pinned by the
138
- operator on 5 August reached no run at all: every run silently used defaults, `env.json` recorded
139
- `"models": {}`, `meta.json` recorded no model because no `-m` was ever passed, and `doctor` kept
140
- confirming the setting because it resolved the path correctly. Three releases shipped that way, and
141
- it surfaced only when a service refused a reasoning level and the command line in the run folder
142
- turned out to name no model. Installed from npm, the file the runner read also sat inside
143
- `node_modules` and was replaced by every update, contradicting the documented promise that
144
- configured profiles survive one. There is now one resolver of the host home for both halves of the
145
- repository, the printed state names the file together with where that path came from, and an
146
- end-to-end test fails if a configured profile stops reaching the assembled Codex command line — the
147
- existing unit tests injected the profile as an argument and could not see the file at all.
148
-
149
- - Every reply now names the worker that ran it: `Model: <model> at <depth> effort (<where the depth
150
- came from>)`, beside the log link. `worker.json` and `meta.json` carry the same profile, including
151
- whether the model came from the configuration or was left to Codex, and whether the depth came
152
- from the request, the configuration or the fallback. A dispatcher reads these rows and nothing
153
- else, and for three releases none of them named a model at all — which is why a run on a model
154
- nobody ordered could go unnoticed. A depth silently served by the fallback is the same silence one
155
- level down, so it is reported the same way. Runs recorded before the field simply keep their old
156
- replies.
157
-
158
- - `minimal` is no longer accepted as a reasoning effort. It passed validation and was then rejected
159
- by the model itself (`Unsupported value: 'minimal' is not supported with the … model`), so an order
160
- died four seconds after launch on quota already spent. The allowed set is now exactly what the
161
- service enumerates: `none`, `low`, `medium`, `high`, `xhigh`, `max`. A validator that admits a
162
- known-dead value is worse than no validator: it moves the refusal past the point where refusing is
163
- free.
164
-
165
- ## [0.5.3] - 2026-08-23
166
-
167
- ### Fixed
168
-
169
- - The worktree lock knew writing by tool name, and a shell writes anyway. It denies edits inside a
170
- repository held by a live `codex-build` run, but was registered only for
171
- `Write|Edit|MultiEdit|NotebookEdit`; on 2026-08-16 a `python - <<PY` heredoc inside `Bash` walked
172
- past it and appended to `CHANGELOG.md`, and the run reported `FAIL — out-of-scope changes (1)`
173
- for work it had finished correctly. Two guards now cover the gap from both sides. The lock's
174
- matcher takes in shell tools as well, and `shell-write-intent.mjs` refuses the obvious forms —
175
- redirection, `tee`, `sed -i`, `cp`, `mv`, `rm`, `touch`, `truncate`, a heredoc fed to an
176
- interpreter — before they run. Behind it, `worktree-witness.mjs` compares the tree after every
177
- shell command against the run's recorded snapshot, drops everything inside the run's declared
178
- scope, and names whatever is left: a run may only write inside its scope, so the rest is another
179
- hand. The recogniser states in its own header that its list is not complete and cannot become one
180
- — that is precisely why the witness stands behind it.
181
-
182
- - The recogniser knew `sed` and did not know `sed.exe`. Its interpreter pattern had always allowed
183
- the suffix — `python.exe` opens a heredoc like `python` does — but the command list compared bare
184
- basenames, so the spelling this platform hands over walked past the guard while the identical
185
- command without the suffix was refused. Found accepting a delegated run of 2026-08-17 that had
186
- recorded the gap as intended behaviour: a test asserting `sed.exe -i` is not a write. A test that
187
- states a defect is true keeps it, so the recogniser now strips the suffix and the case asserts the
188
- refusal.
189
-
190
- - Delegated read-only runs could not execute a single command. codex-cli 0.149.0 on Windows builds
191
- its sandbox from `windows.sandbox` — `elevated` or `unelevated` — and the unelevated token refuses
192
- CreateProcess outright: every command a run tried came back as
193
- `Rejected("… rejected: blocked by policy")` before the process existed. The setting lives in the
194
- operator's `config.toml`, which `--ignore-user-config` drops on purpose, so on 2026-08-23 every
195
- `codex-scout` and `codex-review` run reported honestly that it could not read anything and was
196
- then failed by its own quality gate for having no code references. `codex-build` kept working, and
197
- that looked like "read-only is broken" — but it is the one agent that does not pass
198
- `--ignore-user-config`, so it inherited the key from a line in a personal file the package does
199
- not own; with that flag its `workspace-write` runs are refused just as flatly. Four probes on the
200
- installed CLI, differing by this flag alone, separate the two. All three agents now pass
201
- `-c windows.sandbox=elevated` on Windows, so isolation from the operator's config stays intact and
202
- the sandbox each agent is entitled to is the one it actually gets. The value carries no quotes
203
- because the command line goes through cmd.exe, where a double quote is refused before launch.
204
-
205
- - A hook fix could not reach a host that already had the hook. Installation identifies our entry by
206
- its command and ignores which matcher group it sits under — deliberately, because the matcher
207
- grows whenever a host spelling is added and a lookup by matcher would register a duplicate and
208
- orphan the old entry at uninstall — but nothing afterwards moved the entry to the matcher the
209
- package declares now. A host installed before 2026-08-17 kept `Write|Edit|MultiEdit|NotebookEdit`
210
- for the worktree lock while the package had been saying `…|Bash|PowerShell` for days:
211
- `update --force` reported success and rewrote nothing, so the guard never saw a shell command and
212
- two honest delegated runs were failed for a file written through their window. Merge now moves the
213
- entry under the declared matcher, drops the group it emptied, leaves a group holding someone
214
- else's hooks exactly as it was, and still cannot produce a second registration of our command for
215
- one event.
216
-
217
- - `doctor` stated the matcher this package expects as though it had read it. Its hook line was built
218
- from the registry, so a host still registered under an older matcher was reported as healthy with
219
- the new matcher's text — on 2026-08-17 it printed `…|Bash|PowerShell` over a `settings.json` that
220
- said `Write|Edit|MultiEdit|NotebookEdit`, and the operator had no way to see that the worktree
221
- lock could not see a shell command at all. Same shape as the 2026-08-10 defect in this file:
222
- a check that compares the package with itself cannot fail. The line now prints the matcher the
223
- host recorded, and a difference from the declared one is a `warn` naming both values and
224
- `codex-bridge update --force`.
225
-
226
- - The worktree lock refused four kinds of work it cannot protect. A miss by this guard is
227
- survivable — the witness names the fact afterwards — but a false refusal has no backstop and
228
- simply stops the orchestrator. An ignored path was refused, which made the plans and checklists a
229
- live run is documented in unwritable, although a run is only ever failed for what
230
- `git status` reports; an unexpanded shell variable was resolved against the working directory and
231
- refused as a path inside the repository; a backtick in the TEXT of a heredoc was refused as a
232
- path; and a comparison operator in code (`node -e "i>0?'yes':'no'"`) was read as a redirection.
233
- The lock now passes a path git proves is ignored — and only that, so a missing repository or a
234
- missing git keeps the refusal, while `.git` stays refused whatever git answers — a redirect
235
- target wins over document text (the 2026-08-16 python heredoc is still refused), and a candidate
236
- opening with `$`, `%` or a backtick, or holding a character no file name may contain, is not
237
- reported as a target.
238
-
239
- - Every number this package advertises about itself was wrong. The npm description said "5 guards,
240
- 604 tests" — a count three releases old, next to a registry that had grown a sixth hook the same
241
- week — README said 705 while the suite ran 757, and CLAUDE.md said five guard hooks. Each was
242
- caught by eye during a release, which is how the one in `package.json` survived three of them: a
243
- number written in prose has no reader that can fail. The whole-suite run now compares the size
244
- README claims against what the run reported and exits non-zero naming both, and a test holds the
245
- guard count in README, CLAUDE.md and the package description against `HOOK_DEFINITIONS`. Deleting
246
- the sentence fails the same way an outdated one does; a single-file run reports nothing to
247
- compare and stays silent.
248
-
249
- ## [0.5.2] - 2026-08-16
250
-
251
- ### Fixed
252
-
253
- - The guard punished the very refusal the package demands. A dispatcher whose command the host
254
- denies is told by its own prompt to answer `FAIL`, name its order id and name `codex-bridge
255
- install`; the guard knew only `RUN=` and `ATTACH=`, so that honest answer read as "no run
256
- exists" and was blocked three times before the exhausted try budget let it through. A probe on
257
- 2026-08-16 with `Bash(codex-bridge:*)` denied spent 31.5k tokens on an answer worth ten, and the
258
- counter recorded `{"state": 3}` against it. A host refusal is now a recognised shape beside the
259
- two run lines: a reply that declares failure, names its order id and names the remedy passes on
260
- the first try, before any check that looks for a folder on disk. A reply that declares failure and
261
- names the remedy but omits the order id is blocked with that exact part named — the prompt asked
262
- for it in prose, and prose is a request a model can silently skip.
263
-
264
- - No Claude Code host had registered the three dispatchers since 2026-08-10. The stop-guidance
265
- sentence added that day — `` before `TaskStop`: TaskStop removes the wrapper … `` — was
266
- substituted into the `description:` line of each agent's frontmatter, and an unquoted YAML scalar
267
- cannot contain a colon followed by a space: the frontmatter did not parse, so the agents did not
268
- exist as far as the host was concerned. `doctor` reported every file present the whole time,
269
- because it counted files instead of reading them. Installation now re-emits frontmatter as valid
270
- YAML — any value a reader would not take as a plain scalar is quoted, the sentence survives
271
- verbatim — `doctor` parses each installed definition and checks its `name`, and the suite parses
272
- everything the installer would write.
273
-
274
- - A run ordered under another run's order id is no longer answered with that run's verdict. On
275
- 2026-08-15 a build ordered as `plan42-run3` carried the id of `plan42-run2`, and the runner
276
- printed run2's `OK`, run2's file list and run2's suite over a worktree where nothing had been
277
- done — only a manual `git status` stopped a commit of work that did not exist. An order id names
278
- one task: when the id already belongs to a run whose task text differs, the runner now refuses
279
- with exit code `2` before printing anything, naming the folder that owns the id and the two
280
- remedies (a new `--order-id`, or `--continue`). Repeats of the same task, continuations, and runs
281
- recorded before the `task_hash` field attach exactly as before.
282
- - The same collision is now refused one step earlier and checked one step later. `order-gate` denies
283
- the dispatcher call itself when the ordered id already belongs to a run with a different task —
284
- before any quota is spent — and `reply-guard` blocks a reply whose run records a different
285
- `order_id` than the one the orchestrator ordered, read from the dispatcher's own transcript. Both
286
- read the single definition of "the same task" the runner uses; a second copy of that rule would
287
- drift silently, which is the failure this change exists to end. Every uncertain disk state — an
288
- unreadable task file, a missing runs directory, a run folder written before its `status.json` —
289
- passes, because a guard nobody can predict is a guard everyone works around.
290
- - Delegating to a dispatcher no longer stops on a permission prompt. The shipped agent definitions
291
- started a run by absolute path — `node "<installed dir>/run-codex.mjs" --agent … \ …` — and a host
292
- matches permission rules against the beginning of a command line, so a rule written for this
293
- package could never match it. The only way to start a run is now the package command,
294
- `codex-bridge run`, on a single line: no interpreter, no path, no line continuation. The task
295
- statement travels as `--task-file`, because a heredoc forces the invocation back into the
296
- multi-line form that no rule can cover; stdin remains supported for the direct call, and giving
297
- both channels is a refusal rather than a silent precedence.
298
- - Installed hooks could not run at all. The installer reshaped the source tree — hooks to one
299
- directory, their modules to another — so every hook's relative import was correct in the clone and
300
- false on the host, and all five guards died on `ERR_MODULE_NOT_FOUND` from the day the package
301
- moved to its branded folder. The suite stayed green because it exercises the clone, and `doctor`
302
- called a hook healthy on the strength of a line in `settings.json` without ever starting the file.
303
- The source now contains a literal image of the installed home, `src/home/`, and installation
304
- copies it one to one.
305
- - The reply guard no longer sends a refused dispatcher down the one road the host cannot allow.
306
- Seven of its block texts said "Run run-codex.mjs and return its stdout verbatim" — the sentence
307
- removed from every prompt in this release, because a command line naming a file can match no
308
- permission rule. They were the worst place for it to survive: a dispatcher reads them at the one
309
- moment it is looking for what to do next, having just had its answer rejected. All seven name
310
- `codex-bridge run`. The invariant behind them was the actual defect and is now stated as a rule
311
- rather than as a shape: no line of code in a hook may name the runner file, whole-line comments
312
- excepted. The earlier expression-bound check could not see the text a session is shown when its
313
- turn is ended, which is where the same sentence would have survived a fourth cleanup.
314
- - `doctor` no longer calls a host healthy when an installed agent definition is not this package's.
315
- Drift was advisory — a message, exit code 0 — while an unreadable definition was already fatal. A
316
- checklist run planted the pre-0.5.2 invocation, a file path instead of the package command, in an
317
- installed agent: `doctor` printed the drift and still exited 0, so a script or a CI step would
318
- have called that host healthy while every delegation on it stopped at a permission prompt. Any
319
- definition that differs from the package now exits 1 and names `codex-bridge update --force`.
320
-
321
- ### Added
322
-
323
- - `codex-bridge run` — the public entry point for starting a delegated run, with the same flags,
324
- stdout and exit codes as the historical file invocation.
325
- - Both lines that identify a run to its caller carry the order they belong to:
326
- `RUN=<path> order-id=<id>` and `ATTACH=<path> order-id=<id> started=<time>`. A reply naming a run
327
- cannot be checked against the order that was placed unless the id travels with the folder.
328
- - Two structural guards that make the above regressions loud instead of silent: the install plan
329
- must stay isomorphic to the shipped home image, and every registered hook must actually start as
330
- a child process with its imports resolved.
331
- - `doctor` compares the version answering a hook against the installed copy, verifies the installed
332
- agent definitions against the repository, and downgrades to a warning instead of reporting code it
333
- never ran as healthy.
334
-
335
- ## [0.5.1] - 2026-08-12
336
-
337
- ### Fixed
338
-
339
- - Installing or uninstalling no longer fails over a rules-registry lock that is busy for one more
340
- millisecond. Windows answers a taken lock with three codes, and only one of them is `EEXIST`: a
341
- lock file whose delete is still pending comes back `EPERM`, and a file another process still
342
- holds comes back `EBUSY` — the acquisition loop treated both as fatal instead of waiting. All
343
- three now mean "not yours yet", and the decision lives in one exported check, `isLockTaken()`.
344
-
345
- ### Changed
346
-
347
- - The README's first screen names the problem the package solves, keeps the install command inside
348
- the first 30 lines, adds a second badge row, and shows `doctor` output from a healthy host in
349
- place of a screenshot.
350
- - The reference documentation under `docs/` is English-only now (`overview.ru.md` became
351
- `overview.md`), and the working folders — plans, checklists and audits — left the repository and
352
- its history: a fresh clone contains the five reference documents and nothing else.
353
-
354
- ## [0.5.0] - 2026-08-11
355
-
356
- ### Added
357
-
358
- - A fifth host guard now catches `TaskStop` while a live Codex run is still writing to the
359
- repository, names the run and points to `codex-bridge stop <run>` before allowing a repeated
360
- request. `doctor` and `projects` also surface active runs, so stopping a dispatcher wrapper is
361
- no longer mistaken for stopping its detached worker.
362
- - `codex-bridge hook <name>` gives Claude Code a stable way to invoke each installed guard, and
363
- `meta.json` now records the package version that produced a run. The usage report can therefore
364
- separate historical runs made outside this runner from runs that should satisfy its sandbox
365
- contract.
366
-
367
- ### Changed
368
-
369
- - **Breaking:** the installed package layout now uses `agents/codex-bridge/` instead of
370
- `agents/codex/`, `commands/codex-bridge/` instead of `commands/codex/`, and
371
- `~/.lyupro/.codex-bridge/lib/` for the runner and guards. Slash commands are now
372
- `/codex-bridge:usage` and `/codex-bridge:env`, and hooks in `settings.json` use
373
- `codex-bridge hook <name>` when the matching package version is available. Run
374
- `codex-bridge update` to migrate an existing installation and retire the old layout.
375
- - Runs started without an explicit `--slug` now derive a readable folder name from the required
376
- order id instead of grouping unrelated work under the dispatcher name. Existing run folders
377
- remain discoverable by their order id and task fingerprint.
378
- - The README now leads with the package's guarantees, verification and update guidance, and
379
- limitations before the command reference, and the repository now publishes a security policy
380
- and private vulnerability-reporting route.
381
-
382
- ### Fixed
383
-
384
- - Guards invoked through a global-install symlink no longer exit successfully without running;
385
- the direct-invocation check now compares resolved files, so a silent guard cannot allow every
386
- operation while `doctor` reports it healthy. Hook registration also chooses the short command
387
- form only when the command on `PATH` is this package's exact version, rather than merely present.
388
- - Retiring the previous layout no longer removes the shared Claude Code `agents/` and `commands/`
389
- directories. Seeded operator files are moved only after byte verification, and an interrupted
390
- migration is retried by the next `update` even when the new files are already current.
391
- - A continuation grant that loses its `--continue` flag is now refused before attachment instead
392
- of replaying an older run's verdict as though new work ran. Attachment output also states
393
- explicitly that no new work was started.
394
- - The test suite now isolates both installation roots, preventing tests from writing fixtures or
395
- seeded configuration into the operator's real `~/.lyupro/.codex-bridge/` directory. Migration
396
- dry runs also name the hook event and matcher instead of printing `undefined`.
397
-
398
- ## [0.4.0] - 2026-08-09
399
-
400
- ### Added
401
-
402
- - Scope patterns are checked before Codex starts. A pattern that cannot match anything — an
403
- absolute or drive-qualified path, backslash separators, a `..` segment, or a spelling no
404
- repository file answers to — is refused with no run folder and no quota spent, for all three
405
- dispatchers. The file list comes from `git ls-files --cached --others --exclude-standard`, so
406
- ignored output does not count as repository content. A file the run is meant to create is
407
- declared with `--scope-new`, accepted only by `codex-build` and recorded in `worker.json`.
408
- - `codex-bridge permissions` reports, adds, and removes the optional shell permission rules for
409
- both CLI names and the clone entry point. The exact allow and deny strings merge into
410
- `settings.json`, preserve foreign rules, and are also removed by `uninstall`; `doctor` reports
411
- an absent or partial set as an optional warning.
412
-
413
- ### Changed
414
-
415
- - `codex-bridge sweep` is now `codex-bridge unlock`. The default scope is the current repository;
416
- a bare project name remains addressable, and `--all` is now required for the whole store. The
417
- report shows age, silence, and `alive` / `dead` / `foreign` / `unverified` identity verdicts;
418
- confirmed-alive records are refused with `codex-bridge stop <run>` instead of being closed.
419
-
420
- ### Fixed
421
-
422
- - Run records now verify that a pid still belongs to the process that started the run, using the
423
- process start time and a fresh heartbeat before falling back to an OS probe. A reused or
424
- unverified pid is never signalled by `stop`; `codex-bridge unlock` remains the non-killing way to
425
- close a record whose process identity cannot be confirmed.
426
- - JSON files written by PowerShell with a UTF-8 BOM are now read consistently by the CLI and
427
- installed hooks, and one command run creates at most one settings backup without deleting older
428
- backups.
429
- - 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.
430
- - `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.
431
- - `uninstall --dry-run` names each hook by its own matcher. Three of the four hooks are PreToolUse,
432
- and a lookup by event alone reported `Agent|Task` for the worktree lock and the prune guard too —
433
- in the one line an operator reads to see what a removal is about to touch. The removal itself was
434
- never affected: hooks are found by command, not by matcher.
435
- - The order gate's refusal now shows the line it found. A caller who wrote
436
- `scope (you may create/modify ONLY these):` was told scope was missing, re-read their own prompt,
437
- saw it written there, and had nothing to act on; an autonomous run rewrites the same text forever.
438
- The refusal names the candidate line under the input it belongs to and says why it was not
439
- accepted — a qualifier between the label and the colon, a value that is still a placeholder, or no
440
- separator at all — and stays silent when no candidate exists, because then "the input is missing"
441
- is the truth. The parser accepts exactly what it accepted before: deriving scope from prose would
442
- give away the only guarantee that a run cannot touch files nobody listed.
443
-
444
- ## [0.3.1] - 2026-08-07
445
-
446
- ### Added
447
-
448
- - 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.
449
- - 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.
450
- - `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.
451
-
452
- ### Fixed
453
-
454
- - `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.
455
-
456
- ## [0.3.0] - 2026-08-07
457
-
458
- ### Added
459
-
460
- - `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.
461
- - 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.
462
-
463
- ### Changed
464
-
465
- - 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.
466
- - 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`.
467
- - 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.
468
-
469
- ### Fixed
470
-
471
- - 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.
472
-
473
- ## [0.2.0] - 2026-08-06
474
-
475
- ### Added
476
-
477
- - `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.
478
- - `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.
479
- - 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.
480
- - 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.
481
- - `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.
482
-
483
- ### Changed
484
-
485
- - 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.
486
- - 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.
487
- - 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.
488
- - `log_bytes` in `meta.json` is replaced by `events_bytes` and `stderr_bytes`.
489
-
490
- ### Removed
491
-
492
- - `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.
493
- - 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.
494
-
495
- ### Fixed
496
-
497
- - `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.
498
- - 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.
499
- - 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.
500
- - 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`.
501
- - 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.
502
- - 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.
503
-
504
- ## [0.1.4] - 2026-08-05
505
-
506
- ### Added
507
-
508
- - 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.
509
- - 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.
510
- - 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.
511
-
512
- ### Fixed
513
-
514
- - `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.
515
- - 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.
516
-
517
- ## [0.1.3] - 2026-08-05
518
-
519
- ### Added
520
-
521
- - 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.
522
- - `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.
523
-
524
- ### Changed
525
-
526
- - 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.
527
- - 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.
528
- - 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.
529
-
530
- ## [0.1.2] - 2026-08-04
531
-
532
- ### Fixed
533
-
534
- - 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.
535
- - 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.
536
- - 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.
537
-
538
- ## [0.1.1] - 2026-08-04
539
-
540
- ### Fixed
541
-
542
- - 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.
543
- - 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.
544
- - 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.
545
-
546
- ## [0.1.0] - 2026-08-04
547
-
548
- ### Added
549
-
550
- - Three dispatcher agents for reconnaissance (`codex-scout`), scoped implementation (`codex-build`), and independent review (`codex-review`), each with a structured response contract.
551
- - A delegating runner that separates the launcher from a detached worker, preserves run artifacts after a dropped call, and supports configurable artifact roots.
552
- - 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.
553
- - A reply guard hook that blocks dispatcher replies that do not match the recorded run state or required response shape.
554
- - 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.
555
- - 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.
556
- - 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.
557
- - A 400-line source-file size gate with explicit rationale required for exclusions.
558
- - 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.
559
- - 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.
560
- - Explicit reconnaissance subquestions through a mandatory, repeatable `--question` flag, so coverage is counted against what was ordered rather than against prose the dispatcher rewrote.
561
- - An `answerLanguage` configuration key that fixes the language of a run's answer instead of leaving it to the model.
562
-
563
- ### Changed
564
-
565
- - 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.
566
- - 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.
567
- - 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.
568
- - 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.
569
- - English-language prompts and runner/command output are shipped for the three dispatchers and their supporting commands.
570
- - Installation updates track installed file contents so outdated package files can be refreshed without overwriting hand edits unless `--force` is requested.
571
-
572
- ### Fixed
573
-
574
- - Prevented environment and service writes from being misreported as delegated work while keeping them visible in run results.
575
- - Prevented different repositories with the same directory name from sharing run history.
576
- - Prevented malformed state files, including files with a byte-order mark, from silently bypassing abandoned-run detection, busy-tree locking, and chain lookup.
577
- - Fixed diagnostics to report the run folder actually used by the runner and to continue reporting remaining checks when a project marker is unreadable.
578
- - 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.2] - 2026-09-07
8
+
9
+ ### Fixed
10
+
11
+ - No command greets the operator with a deprecation warning of its own. On Node 24 every `install`,
12
+ `update` and `doctor` printed `[DEP0190] DeprecationWarning: Passing args to a child process with
13
+ shell option true` ahead of its useful output, because the check that asks the command on PATH
14
+ for its version ran through a shell — the only way to execute the `.cmd` shim npm writes on
15
+ Windows. The shim is now located on PATH and executed directly, and the interpreter comes from
16
+ `ComSpec` instead of the PATH handed in, so a trimmed PATH cannot turn the check into a silent
17
+ "no version".
18
+
19
+ ### Changed
20
+
21
+ - A guard fails the suite if a child process in `src` or `cli` is started through a shell again:
22
+ the `shell` option may only be the literal `false`, and `exec`/`execSync` may not be imported at
23
+ all. What this release fixes was not spelled `shell: true` but `shell: process.platform ===
24
+ 'win32'`, so a guard bound to the literal would have missed the line it exists for.
25
+ - The hook-registration test that claimed to cover a command of another version handed a directory
26
+ holding no `codex-bridge` and exercised the unreachable branch instead. It now runs a fixture
27
+ shim in a directory named with a space, brackets and an ampersand, covering both branches and,
28
+ for the first time, that a version is read at all.
29
+
30
+ ## [0.6.1] - 2026-09-07
31
+
32
+ ### Fixed
33
+
34
+ - `update` names the package it acted from. Standing in a clone while an older copy answers from
35
+ PATH, the command compared the home against its own files and said `codex-bridge is up to date` —
36
+ true about the package that ran, silent about the one on screen, and the cause had to be found by
37
+ hand. Every outcome now carries the name, version and path of the copy that answered, and when
38
+ that copy is not the checkout the operator is standing in, a second line names both versions and
39
+ the command that changes which one answers.
40
+ - `permissions add` reports the state of the rule set instead of the size of its own change. A
41
+ complete set answered `Added 0 permission rule strings.`, where zero reads as a refusal rather
42
+ than as "all of them are already there".
43
+
44
+ ## [0.6.0] - 2026-09-07
45
+
46
+ ### Added
47
+
48
+ - `codex-bridge model` answers which model and reasoning depth each of the three roles runs on, and
49
+ where each value comes from — the operator's config file or the package default. The config is one
50
+ file per machine, shared by every project on it, and the command says so: changing a model here
51
+ changes the executor for every repository this host delegates from.
52
+ - `codex-bridge model list` prints the live catalogue, hidden models included and marked as hidden.
53
+ A model Codex does not advertise is still a model the operator may choose; omitting it would mean
54
+ deciding for them and then explaining why the system has a model the list denies.
55
+ - `codex-bridge model set <role> <model> [effort]` and `model unset <role>` write that choice. The
56
+ pair is checked against the catalogue entry of that very model at the moment of writing, so
57
+ `ultra` — supported by the newest model and rejected by the package's own flat list until now —
58
+ became reachable, and a depth the chosen model does not offer is refused with its supported
59
+ depths named. Nothing is substituted silently: the command never changes what it was not asked to.
60
+ - `codex-bridge model speed <role> <tier>` pins a paid acceleration tier, and `model speed <role>
61
+ unset` removes it. The tier is spelled with the identifier the catalogue reports for that model,
62
+ never a word held in the package, and the command writes nothing until a second call ends in
63
+ `confirm` — the first prints the catalogue's own description of the tier and a link to the page
64
+ carrying the credit multipliers. The installer now also places one `ask` rule of its own, so the
65
+ host asks the operator before an agent can run that command at all.
66
+ - A pinned tier reaches the run: the launcher passes `-c service_tier=<id>` when, and only when, a
67
+ role has one pinned.
68
+
69
+ ### Changed
70
+
71
+ - The depth of a run is no longer validated against a list held in the package. That list had
72
+ drifted from the server in both directions — it offered a depth no model accepts and refused one
73
+ the newest model supports — so the writing command asks the catalogue and the hot path checks only
74
+ the shape of the value. The arbiter at run time is Codex itself, whose refusal is more accurate
75
+ than a copy of its data.
76
+ - One boundary now owns every write to the run config, and it persists only the edit it was given.
77
+ Reading merges the package defaults, and the previous writer saved that merged object, so a single
78
+ edit would have frozen every default into the operator's file — worst of all the list of
79
+ environment paths, which the package extends as it learns of new ones and which decides whether a
80
+ change belongs to the run or to the environment.
81
+ - The word `mode` no longer names three different things in this package. The reviewer's flag is
82
+ `--changeset`, the prune says which strategy, the table which truncation, and one registry answers
83
+ who the three agents are.
84
+ - The README states the suite size it actually has.
85
+
86
+ ### Fixed
87
+
88
+ - Two edits of the config racing each other can no longer lose one of them. Both commands read the
89
+ role profiles, waited seconds for the catalogue, then wrote what they had read, and three
90
+ concurrent processes all reported success while one edit was missing from the file. The edit
91
+ boundary now reads the file itself, hands the fresh value to the caller's transformer, compares
92
+ the bytes immediately before publishing, and holds the lock this package already used for the
93
+ rules-ownership registry — one definition now serves both writers.
94
+ - The worktree lock reads the shell rather than the prose someone wrote into a document. A plain
95
+ `cat > file << EOF` handed the whole command, heredoc body included, to the analysis, so the word
96
+ `touch` inside an English sentence made the next words look like files being written. The reverse
97
+ hole closed with it: the tail after a heredoc's closing marker was not parsed at all.
98
+ - A dead run stays dead even when Windows hands its process id to someone else.
99
+ - The reader of the Codex user configuration answers about configuration, not about prose inside it.
100
+ A multi-line TOML literal could contain a line indistinguishable from an assignment, and it was
101
+ read as the setting; an empty argument threw past the module's own error boundary, which exists so
102
+ the table renders whatever state the file is in.
103
+
104
+ ## [0.5.5] - 2026-08-28
105
+
106
+ ### Fixed
107
+
108
+ - The write guard now reads quoting, so a redirect character inside a quoted argument is text rather
109
+ than an operator. While a delegated run held the repository, `sed -E 's#(A ).*#<redacted>#'` was
110
+ refused as a write to a file named `#'`: the scan ran over raw text, so the `>` looked like a
111
+ redirect and the token after it looked like its destination. The file already carried a defence
112
+ against this class, added after a `node -e` expression was misread the same way, but that defence
113
+ filtered candidates holding characters no file name may contain and the one refused here held
114
+ none. The guard still fails closed: when quotes cannot be balanced the scan behaves exactly as
115
+ before, because answering "no write here" to a line it cannot parse would be worse than the false
116
+ refusal being removed.
117
+ - The installation summary no longer reads as a bug. Four `PreToolUse` guards printed as
118
+ `PreToolUse and PreToolUse and PreToolUse and PreToolUse` because the sentence joined every hook's
119
+ event; it now groups by event and counts.
120
+ - The installed-file count agrees with `doctor`. The summary counted the Codex rules file among the
121
+ files while `doctor` reports it on its own line, so `68` sat next to `67` and read as a lost file.
122
+ - `Granted 0 permission rule(s)` no longer reports the good outcome as if it were a failure. When
123
+ the set is complete the summary says so; on a host whose settings were edited by hand it says how
124
+ many rules are missing and how to restore them, instead of claiming all are in place while the
125
+ operator gets permission windows. The already-installed branch reports the set by reading it —
126
+ granting there would have restored a deleted rule under the heading "nothing to do".
127
+ - A contract probe that refuses to judge now carries the host's own words. `The host exited with
128
+ status 1.` was the entire account of a failure on a live host; the last non-empty line the host
129
+ printed is now part of the reason. What counts as honoured, ignored or inconclusive is unchanged.
130
+
131
+ ## [0.5.4] - 2026-08-28
132
+
133
+ ### Added
134
+
135
+ - A gate that holds every registered hook to one rule: a hook may not add anything to what the host
136
+ runs. A neighbouring package spent a whole plan undoing the opposite habit — its `PreToolUse` hook
137
+ wrapped the agent's command in a pipe, and because a host permission rule is matched against the
138
+ beginning of the final string and is not applied at all once that string carries a substitution or
139
+ a compound operator, every command it touched became unresolvable by the operator's own rules.
140
+ This package reported those windows on 2026-08-15 while carrying none of the cause. The gate reads
141
+ both behaviour and sources: a hook may return only the keys that refuse or speak
142
+ (`permissionDecision` of `deny` or `ask`, `permissionDecisionReason`, `additionalContext`), and no
143
+ hook source may mention a key that replaces what the host is about to run. `permissionDecision:
144
+ "allow"` is refused as well — the host stopped honouring it between 2.1.119 and 2.1.231, and while
145
+ it worked it was a blanket auto-approve over the operator's own deny rules.
146
+
147
+ - `doctor` and `install` now say whether this host still honours a hook refusal. Every guard in this
148
+ package refuses work with `permissionDecision: "deny"`, and the sibling key of that same object
149
+ died quietly — a neighbouring package established that `"allow"` stopped being honoured somewhere
150
+ between host 2.1.119 and 2.1.231, with no error printed on the way. Nothing here would have
151
+ noticed `deny` going the same way, because the suite checks that a hook RETURNED a refusal, never
152
+ that the host APPLIED it. The record is bound to a host version, since that is what changes
153
+ underneath an installation, and reports five states: never probed, probed on another version,
154
+ refusals ignored (which names the guards it makes inert and exits non-zero), verified, and a host
155
+ whose version cannot be read.
156
+
157
+ - `codex-bridge doctor --probe-contract` performs that measurement on a live host, and every message
158
+ that a probe could change now names it. The probe builds a throwaway rig outside the repository,
159
+ registers one temporary hook there that refuses one marker command, runs the real host inside it
160
+ and judges by whether the marker file appeared. Three conditions were bought by a measurement that
161
+ first reported a working host as dead: only the tool under test is allowed (`--allowedTools Bash`),
162
+ because the nested session otherwise sidesteps the refusal with `Write` and writes its own report
163
+ of being blocked into the marker file; the verdict comes from what executed, never from what the
164
+ model said about it; and settings are read from the project alone, or the operator's own hooks get
165
+ measured instead. A crash, a timeout or a hook that never fired records nothing and exits 2 — an
166
+ absent marker is only evidence when the host survived to the end, and a probe that announced a
167
+ contract as verified without measuring it would be the very silence this record exists to break.
168
+
169
+ ### Fixed
170
+
171
+ - A run now reads the operator's configuration instead of the copy shipped inside the package. The
172
+ installer seeds `config.json` into `~/.lyupro/.codex-bridge/`, the CLI read it from there, and the
173
+ runner built its own path from the module it happened to live in — so `codex-bridge run` opened the
174
+ package's seed, in which no profile is ever configured. A model and a reasoning depth pinned by the
175
+ operator on 5 August reached no run at all: every run silently used defaults, `env.json` recorded
176
+ `"models": {}`, `meta.json` recorded no model because no `-m` was ever passed, and `doctor` kept
177
+ confirming the setting because it resolved the path correctly. Three releases shipped that way, and
178
+ it surfaced only when a service refused a reasoning level and the command line in the run folder
179
+ turned out to name no model. Installed from npm, the file the runner read also sat inside
180
+ `node_modules` and was replaced by every update, contradicting the documented promise that
181
+ configured profiles survive one. There is now one resolver of the host home for both halves of the
182
+ repository, the printed state names the file together with where that path came from, and an
183
+ end-to-end test fails if a configured profile stops reaching the assembled Codex command line — the
184
+ existing unit tests injected the profile as an argument and could not see the file at all.
185
+
186
+ - Every reply now names the worker that ran it: `Model: <model> at <depth> effort (<where the depth
187
+ came from>)`, beside the log link. `worker.json` and `meta.json` carry the same profile, including
188
+ whether the model came from the configuration or was left to Codex, and whether the depth came
189
+ from the request, the configuration or the fallback. A dispatcher reads these rows and nothing
190
+ else, and for three releases none of them named a model at all — which is why a run on a model
191
+ nobody ordered could go unnoticed. A depth silently served by the fallback is the same silence one
192
+ level down, so it is reported the same way. Runs recorded before the field simply keep their old
193
+ replies.
194
+
195
+ - `minimal` is no longer accepted as a reasoning effort. It passed validation and was then rejected
196
+ by the model itself (`Unsupported value: 'minimal' is not supported with the … model`), so an order
197
+ died four seconds after launch on quota already spent. The allowed set is now exactly what the
198
+ service enumerates: `none`, `low`, `medium`, `high`, `xhigh`, `max`. A validator that admits a
199
+ known-dead value is worse than no validator: it moves the refusal past the point where refusing is
200
+ free.
201
+
202
+ ## [0.5.3] - 2026-08-23
203
+
204
+ ### Fixed
205
+
206
+ - The worktree lock knew writing by tool name, and a shell writes anyway. It denies edits inside a
207
+ repository held by a live `codex-build` run, but was registered only for
208
+ `Write|Edit|MultiEdit|NotebookEdit`; on 2026-08-16 a `python - <<PY` heredoc inside `Bash` walked
209
+ past it and appended to `CHANGELOG.md`, and the run reported `FAIL — out-of-scope changes (1)`
210
+ for work it had finished correctly. Two guards now cover the gap from both sides. The lock's
211
+ matcher takes in shell tools as well, and `shell-write-intent.mjs` refuses the obvious forms —
212
+ redirection, `tee`, `sed -i`, `cp`, `mv`, `rm`, `touch`, `truncate`, a heredoc fed to an
213
+ interpreter — before they run. Behind it, `worktree-witness.mjs` compares the tree after every
214
+ shell command against the run's recorded snapshot, drops everything inside the run's declared
215
+ scope, and names whatever is left: a run may only write inside its scope, so the rest is another
216
+ hand. The recogniser states in its own header that its list is not complete and cannot become one
217
+ — that is precisely why the witness stands behind it.
218
+
219
+ - The recogniser knew `sed` and did not know `sed.exe`. Its interpreter pattern had always allowed
220
+ the suffix — `python.exe` opens a heredoc like `python` does — but the command list compared bare
221
+ basenames, so the spelling this platform hands over walked past the guard while the identical
222
+ command without the suffix was refused. Found accepting a delegated run of 2026-08-17 that had
223
+ recorded the gap as intended behaviour: a test asserting `sed.exe -i` is not a write. A test that
224
+ states a defect is true keeps it, so the recogniser now strips the suffix and the case asserts the
225
+ refusal.
226
+
227
+ - Delegated read-only runs could not execute a single command. codex-cli 0.149.0 on Windows builds
228
+ its sandbox from `windows.sandbox` — `elevated` or `unelevated` — and the unelevated token refuses
229
+ CreateProcess outright: every command a run tried came back as
230
+ `Rejected("… rejected: blocked by policy")` before the process existed. The setting lives in the
231
+ operator's `config.toml`, which `--ignore-user-config` drops on purpose, so on 2026-08-23 every
232
+ `codex-scout` and `codex-review` run reported honestly that it could not read anything and was
233
+ then failed by its own quality gate for having no code references. `codex-build` kept working, and
234
+ that looked like "read-only is broken" — but it is the one agent that does not pass
235
+ `--ignore-user-config`, so it inherited the key from a line in a personal file the package does
236
+ not own; with that flag its `workspace-write` runs are refused just as flatly. Four probes on the
237
+ installed CLI, differing by this flag alone, separate the two. All three agents now pass
238
+ `-c windows.sandbox=elevated` on Windows, so isolation from the operator's config stays intact and
239
+ the sandbox each agent is entitled to is the one it actually gets. The value carries no quotes
240
+ because the command line goes through cmd.exe, where a double quote is refused before launch.
241
+
242
+ - A hook fix could not reach a host that already had the hook. Installation identifies our entry by
243
+ its command and ignores which matcher group it sits under — deliberately, because the matcher
244
+ grows whenever a host spelling is added and a lookup by matcher would register a duplicate and
245
+ orphan the old entry at uninstall — but nothing afterwards moved the entry to the matcher the
246
+ package declares now. A host installed before 2026-08-17 kept `Write|Edit|MultiEdit|NotebookEdit`
247
+ for the worktree lock while the package had been saying `…|Bash|PowerShell` for days:
248
+ `update --force` reported success and rewrote nothing, so the guard never saw a shell command and
249
+ two honest delegated runs were failed for a file written through their window. Merge now moves the
250
+ entry under the declared matcher, drops the group it emptied, leaves a group holding someone
251
+ else's hooks exactly as it was, and still cannot produce a second registration of our command for
252
+ one event.
253
+
254
+ - `doctor` stated the matcher this package expects as though it had read it. Its hook line was built
255
+ from the registry, so a host still registered under an older matcher was reported as healthy with
256
+ the new matcher's text — on 2026-08-17 it printed `…|Bash|PowerShell` over a `settings.json` that
257
+ said `Write|Edit|MultiEdit|NotebookEdit`, and the operator had no way to see that the worktree
258
+ lock could not see a shell command at all. Same shape as the 2026-08-10 defect in this file:
259
+ a check that compares the package with itself cannot fail. The line now prints the matcher the
260
+ host recorded, and a difference from the declared one is a `warn` naming both values and
261
+ `codex-bridge update --force`.
262
+
263
+ - The worktree lock refused four kinds of work it cannot protect. A miss by this guard is
264
+ survivable — the witness names the fact afterwards — but a false refusal has no backstop and
265
+ simply stops the orchestrator. An ignored path was refused, which made the plans and checklists a
266
+ live run is documented in unwritable, although a run is only ever failed for what
267
+ `git status` reports; an unexpanded shell variable was resolved against the working directory and
268
+ refused as a path inside the repository; a backtick in the TEXT of a heredoc was refused as a
269
+ path; and a comparison operator in code (`node -e "i>0?'yes':'no'"`) was read as a redirection.
270
+ The lock now passes a path git proves is ignored — and only that, so a missing repository or a
271
+ missing git keeps the refusal, while `.git` stays refused whatever git answers — a redirect
272
+ target wins over document text (the 2026-08-16 python heredoc is still refused), and a candidate
273
+ opening with `$`, `%` or a backtick, or holding a character no file name may contain, is not
274
+ reported as a target.
275
+
276
+ - Every number this package advertises about itself was wrong. The npm description said "5 guards,
277
+ 604 tests" — a count three releases old, next to a registry that had grown a sixth hook the same
278
+ week — README said 705 while the suite ran 757, and CLAUDE.md said five guard hooks. Each was
279
+ caught by eye during a release, which is how the one in `package.json` survived three of them: a
280
+ number written in prose has no reader that can fail. The whole-suite run now compares the size
281
+ README claims against what the run reported and exits non-zero naming both, and a test holds the
282
+ guard count in README, CLAUDE.md and the package description against `HOOK_DEFINITIONS`. Deleting
283
+ the sentence fails the same way an outdated one does; a single-file run reports nothing to
284
+ compare and stays silent.
285
+
286
+ ## [0.5.2] - 2026-08-16
287
+
288
+ ### Fixed
289
+
290
+ - The guard punished the very refusal the package demands. A dispatcher whose command the host
291
+ denies is told by its own prompt to answer `FAIL`, name its order id and name `codex-bridge
292
+ install`; the guard knew only `RUN=` and `ATTACH=`, so that honest answer read as "no run
293
+ exists" and was blocked three times before the exhausted try budget let it through. A probe on
294
+ 2026-08-16 with `Bash(codex-bridge:*)` denied spent 31.5k tokens on an answer worth ten, and the
295
+ counter recorded `{"state": 3}` against it. A host refusal is now a recognised shape beside the
296
+ two run lines: a reply that declares failure, names its order id and names the remedy passes on
297
+ the first try, before any check that looks for a folder on disk. A reply that declares failure and
298
+ names the remedy but omits the order id is blocked with that exact part named — the prompt asked
299
+ for it in prose, and prose is a request a model can silently skip.
300
+
301
+ - No Claude Code host had registered the three dispatchers since 2026-08-10. The stop-guidance
302
+ sentence added that day — `` before `TaskStop`: TaskStop removes the wrapper … `` — was
303
+ substituted into the `description:` line of each agent's frontmatter, and an unquoted YAML scalar
304
+ cannot contain a colon followed by a space: the frontmatter did not parse, so the agents did not
305
+ exist as far as the host was concerned. `doctor` reported every file present the whole time,
306
+ because it counted files instead of reading them. Installation now re-emits frontmatter as valid
307
+ YAML — any value a reader would not take as a plain scalar is quoted, the sentence survives
308
+ verbatim — `doctor` parses each installed definition and checks its `name`, and the suite parses
309
+ everything the installer would write.
310
+
311
+ - A run ordered under another run's order id is no longer answered with that run's verdict. On
312
+ 2026-08-15 a build ordered as `plan42-run3` carried the id of `plan42-run2`, and the runner
313
+ printed run2's `OK`, run2's file list and run2's suite over a worktree where nothing had been
314
+ done — only a manual `git status` stopped a commit of work that did not exist. An order id names
315
+ one task: when the id already belongs to a run whose task text differs, the runner now refuses
316
+ with exit code `2` before printing anything, naming the folder that owns the id and the two
317
+ remedies (a new `--order-id`, or `--continue`). Repeats of the same task, continuations, and runs
318
+ recorded before the `task_hash` field attach exactly as before.
319
+ - The same collision is now refused one step earlier and checked one step later. `order-gate` denies
320
+ the dispatcher call itself when the ordered id already belongs to a run with a different task —
321
+ before any quota is spent — and `reply-guard` blocks a reply whose run records a different
322
+ `order_id` than the one the orchestrator ordered, read from the dispatcher's own transcript. Both
323
+ read the single definition of "the same task" the runner uses; a second copy of that rule would
324
+ drift silently, which is the failure this change exists to end. Every uncertain disk state — an
325
+ unreadable task file, a missing runs directory, a run folder written before its `status.json` —
326
+ passes, because a guard nobody can predict is a guard everyone works around.
327
+ - Delegating to a dispatcher no longer stops on a permission prompt. The shipped agent definitions
328
+ started a run by absolute path — `node "<installed dir>/run-codex.mjs" --agent … \ …` — and a host
329
+ matches permission rules against the beginning of a command line, so a rule written for this
330
+ package could never match it. The only way to start a run is now the package command,
331
+ `codex-bridge run`, on a single line: no interpreter, no path, no line continuation. The task
332
+ statement travels as `--task-file`, because a heredoc forces the invocation back into the
333
+ multi-line form that no rule can cover; stdin remains supported for the direct call, and giving
334
+ both channels is a refusal rather than a silent precedence.
335
+ - Installed hooks could not run at all. The installer reshaped the source tree — hooks to one
336
+ directory, their modules to another — so every hook's relative import was correct in the clone and
337
+ false on the host, and all five guards died on `ERR_MODULE_NOT_FOUND` from the day the package
338
+ moved to its branded folder. The suite stayed green because it exercises the clone, and `doctor`
339
+ called a hook healthy on the strength of a line in `settings.json` without ever starting the file.
340
+ The source now contains a literal image of the installed home, `src/home/`, and installation
341
+ copies it one to one.
342
+ - The reply guard no longer sends a refused dispatcher down the one road the host cannot allow.
343
+ Seven of its block texts said "Run run-codex.mjs and return its stdout verbatim" — the sentence
344
+ removed from every prompt in this release, because a command line naming a file can match no
345
+ permission rule. They were the worst place for it to survive: a dispatcher reads them at the one
346
+ moment it is looking for what to do next, having just had its answer rejected. All seven name
347
+ `codex-bridge run`. The invariant behind them was the actual defect and is now stated as a rule
348
+ rather than as a shape: no line of code in a hook may name the runner file, whole-line comments
349
+ excepted. The earlier expression-bound check could not see the text a session is shown when its
350
+ turn is ended, which is where the same sentence would have survived a fourth cleanup.
351
+ - `doctor` no longer calls a host healthy when an installed agent definition is not this package's.
352
+ Drift was advisory — a message, exit code 0 — while an unreadable definition was already fatal. A
353
+ checklist run planted the pre-0.5.2 invocation, a file path instead of the package command, in an
354
+ installed agent: `doctor` printed the drift and still exited 0, so a script or a CI step would
355
+ have called that host healthy while every delegation on it stopped at a permission prompt. Any
356
+ definition that differs from the package now exits 1 and names `codex-bridge update --force`.
357
+
358
+ ### Added
359
+
360
+ - `codex-bridge run` — the public entry point for starting a delegated run, with the same flags,
361
+ stdout and exit codes as the historical file invocation.
362
+ - Both lines that identify a run to its caller carry the order they belong to:
363
+ `RUN=<path> order-id=<id>` and `ATTACH=<path> order-id=<id> started=<time>`. A reply naming a run
364
+ cannot be checked against the order that was placed unless the id travels with the folder.
365
+ - Two structural guards that make the above regressions loud instead of silent: the install plan
366
+ must stay isomorphic to the shipped home image, and every registered hook must actually start as
367
+ a child process with its imports resolved.
368
+ - `doctor` compares the version answering a hook against the installed copy, verifies the installed
369
+ agent definitions against the repository, and downgrades to a warning instead of reporting code it
370
+ never ran as healthy.
371
+
372
+ ## [0.5.1] - 2026-08-12
373
+
374
+ ### Fixed
375
+
376
+ - Installing or uninstalling no longer fails over a rules-registry lock that is busy for one more
377
+ millisecond. Windows answers a taken lock with three codes, and only one of them is `EEXIST`: a
378
+ lock file whose delete is still pending comes back `EPERM`, and a file another process still
379
+ holds comes back `EBUSY` — the acquisition loop treated both as fatal instead of waiting. All
380
+ three now mean "not yours yet", and the decision lives in one exported check, `isLockTaken()`.
381
+
382
+ ### Changed
383
+
384
+ - The README's first screen names the problem the package solves, keeps the install command inside
385
+ the first 30 lines, adds a second badge row, and shows `doctor` output from a healthy host in
386
+ place of a screenshot.
387
+ - The reference documentation under `docs/` is English-only now (`overview.ru.md` became
388
+ `overview.md`), and the working folders — plans, checklists and audits — left the repository and
389
+ its history: a fresh clone contains the five reference documents and nothing else.
390
+
391
+ ## [0.5.0] - 2026-08-11
392
+
393
+ ### Added
394
+
395
+ - A fifth host guard now catches `TaskStop` while a live Codex run is still writing to the
396
+ repository, names the run and points to `codex-bridge stop <run>` before allowing a repeated
397
+ request. `doctor` and `projects` also surface active runs, so stopping a dispatcher wrapper is
398
+ no longer mistaken for stopping its detached worker.
399
+ - `codex-bridge hook <name>` gives Claude Code a stable way to invoke each installed guard, and
400
+ `meta.json` now records the package version that produced a run. The usage report can therefore
401
+ separate historical runs made outside this runner from runs that should satisfy its sandbox
402
+ contract.
403
+
404
+ ### Changed
405
+
406
+ - **Breaking:** the installed package layout now uses `agents/codex-bridge/` instead of
407
+ `agents/codex/`, `commands/codex-bridge/` instead of `commands/codex/`, and
408
+ `~/.lyupro/.codex-bridge/lib/` for the runner and guards. Slash commands are now
409
+ `/codex-bridge:usage` and `/codex-bridge:env`, and hooks in `settings.json` use
410
+ `codex-bridge hook <name>` when the matching package version is available. Run
411
+ `codex-bridge update` to migrate an existing installation and retire the old layout.
412
+ - Runs started without an explicit `--slug` now derive a readable folder name from the required
413
+ order id instead of grouping unrelated work under the dispatcher name. Existing run folders
414
+ remain discoverable by their order id and task fingerprint.
415
+ - The README now leads with the package's guarantees, verification and update guidance, and
416
+ limitations before the command reference, and the repository now publishes a security policy
417
+ and private vulnerability-reporting route.
418
+
419
+ ### Fixed
420
+
421
+ - Guards invoked through a global-install symlink no longer exit successfully without running;
422
+ the direct-invocation check now compares resolved files, so a silent guard cannot allow every
423
+ operation while `doctor` reports it healthy. Hook registration also chooses the short command
424
+ form only when the command on `PATH` is this package's exact version, rather than merely present.
425
+ - Retiring the previous layout no longer removes the shared Claude Code `agents/` and `commands/`
426
+ directories. Seeded operator files are moved only after byte verification, and an interrupted
427
+ migration is retried by the next `update` even when the new files are already current.
428
+ - A continuation grant that loses its `--continue` flag is now refused before attachment instead
429
+ of replaying an older run's verdict as though new work ran. Attachment output also states
430
+ explicitly that no new work was started.
431
+ - The test suite now isolates both installation roots, preventing tests from writing fixtures or
432
+ seeded configuration into the operator's real `~/.lyupro/.codex-bridge/` directory. Migration
433
+ dry runs also name the hook event and matcher instead of printing `undefined`.
434
+
435
+ ## [0.4.0] - 2026-08-09
436
+
437
+ ### Added
438
+
439
+ - Scope patterns are checked before Codex starts. A pattern that cannot match anything — an
440
+ absolute or drive-qualified path, backslash separators, a `..` segment, or a spelling no
441
+ repository file answers to — is refused with no run folder and no quota spent, for all three
442
+ dispatchers. The file list comes from `git ls-files --cached --others --exclude-standard`, so
443
+ ignored output does not count as repository content. A file the run is meant to create is
444
+ declared with `--scope-new`, accepted only by `codex-build` and recorded in `worker.json`.
445
+ - `codex-bridge permissions` reports, adds, and removes the optional shell permission rules for
446
+ both CLI names and the clone entry point. The exact allow and deny strings merge into
447
+ `settings.json`, preserve foreign rules, and are also removed by `uninstall`; `doctor` reports
448
+ an absent or partial set as an optional warning.
449
+
450
+ ### Changed
451
+
452
+ - `codex-bridge sweep` is now `codex-bridge unlock`. The default scope is the current repository;
453
+ a bare project name remains addressable, and `--all` is now required for the whole store. The
454
+ report shows age, silence, and `alive` / `dead` / `foreign` / `unverified` identity verdicts;
455
+ confirmed-alive records are refused with `codex-bridge stop <run>` instead of being closed.
456
+
457
+ ### Fixed
458
+
459
+ - Run records now verify that a pid still belongs to the process that started the run, using the
460
+ process start time and a fresh heartbeat before falling back to an OS probe. A reused or
461
+ unverified pid is never signalled by `stop`; `codex-bridge unlock` remains the non-killing way to
462
+ close a record whose process identity cannot be confirmed.
463
+ - JSON files written by PowerShell with a UTF-8 BOM are now read consistently by the CLI and
464
+ installed hooks, and one command run creates at most one settings backup without deleting older
465
+ backups.
466
+ - 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.
467
+ - `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.
468
+ - `uninstall --dry-run` names each hook by its own matcher. Three of the four hooks are PreToolUse,
469
+ and a lookup by event alone reported `Agent|Task` for the worktree lock and the prune guard too —
470
+ in the one line an operator reads to see what a removal is about to touch. The removal itself was
471
+ never affected: hooks are found by command, not by matcher.
472
+ - The order gate's refusal now shows the line it found. A caller who wrote
473
+ `scope (you may create/modify ONLY these):` was told scope was missing, re-read their own prompt,
474
+ saw it written there, and had nothing to act on; an autonomous run rewrites the same text forever.
475
+ The refusal names the candidate line under the input it belongs to and says why it was not
476
+ accepted — a qualifier between the label and the colon, a value that is still a placeholder, or no
477
+ separator at all — and stays silent when no candidate exists, because then "the input is missing"
478
+ is the truth. The parser accepts exactly what it accepted before: deriving scope from prose would
479
+ give away the only guarantee that a run cannot touch files nobody listed.
480
+
481
+ ## [0.3.1] - 2026-08-07
482
+
483
+ ### Added
484
+
485
+ - 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.
486
+ - 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.
487
+ - `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.
488
+
489
+ ### Fixed
490
+
491
+ - `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.
492
+
493
+ ## [0.3.0] - 2026-08-07
494
+
495
+ ### Added
496
+
497
+ - `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.
498
+ - 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.
499
+
500
+ ### Changed
501
+
502
+ - 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.
503
+ - 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`.
504
+ - 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.
505
+
506
+ ### Fixed
507
+
508
+ - 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.
509
+
510
+ ## [0.2.0] - 2026-08-06
511
+
512
+ ### Added
513
+
514
+ - `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.
515
+ - `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.
516
+ - 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.
517
+ - 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.
518
+ - `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.
519
+
520
+ ### Changed
521
+
522
+ - 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.
523
+ - 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.
524
+ - 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.
525
+ - `log_bytes` in `meta.json` is replaced by `events_bytes` and `stderr_bytes`.
526
+
527
+ ### Removed
528
+
529
+ - `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.
530
+ - 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.
531
+
532
+ ### Fixed
533
+
534
+ - `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.
535
+ - 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.
536
+ - 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.
537
+ - 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`.
538
+ - 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.
539
+ - 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.
540
+
541
+ ## [0.1.4] - 2026-08-05
542
+
543
+ ### Added
544
+
545
+ - 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.
546
+ - 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.
547
+ - 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.
548
+
549
+ ### Fixed
550
+
551
+ - `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.
552
+ - 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.
553
+
554
+ ## [0.1.3] - 2026-08-05
555
+
556
+ ### Added
557
+
558
+ - 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.
559
+ - `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.
560
+
561
+ ### Changed
562
+
563
+ - 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.
564
+ - 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.
565
+ - 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.
566
+
567
+ ## [0.1.2] - 2026-08-04
568
+
569
+ ### Fixed
570
+
571
+ - 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.
572
+ - 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.
573
+ - 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.
574
+
575
+ ## [0.1.1] - 2026-08-04
576
+
577
+ ### Fixed
578
+
579
+ - 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.
580
+ - 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.
581
+ - 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.
582
+
583
+ ## [0.1.0] - 2026-08-04
584
+
585
+ ### Added
586
+
587
+ - Three dispatcher agents for reconnaissance (`codex-scout`), scoped implementation (`codex-build`), and independent review (`codex-review`), each with a structured response contract.
588
+ - A delegating runner that separates the launcher from a detached worker, preserves run artifacts after a dropped call, and supports configurable artifact roots.
589
+ - 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.
590
+ - A reply guard hook that blocks dispatcher replies that do not match the recorded run state or required response shape.
591
+ - 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.
592
+ - 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.
593
+ - 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.
594
+ - A 400-line source-file size gate with explicit rationale required for exclusions.
595
+ - 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.
596
+ - 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.
597
+ - Explicit reconnaissance subquestions through a mandatory, repeatable `--question` flag, so coverage is counted against what was ordered rather than against prose the dispatcher rewrote.
598
+ - An `answerLanguage` configuration key that fixes the language of a run's answer instead of leaving it to the model.
599
+
600
+ ### Changed
601
+
602
+ - 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.
603
+ - 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.
604
+ - 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.
605
+ - 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.
606
+ - English-language prompts and runner/command output are shipped for the three dispatchers and their supporting commands.
607
+ - Installation updates track installed file contents so outdated package files can be refreshed without overwriting hand edits unless `--force` is requested.
608
+
609
+ ### Fixed
610
+
611
+ - Prevented environment and service writes from being misreported as delegated work while keeping them visible in run results.
612
+ - Prevented different repositories with the same directory name from sharing run history.
613
+ - Prevented malformed state files, including files with a byte-order mark, from silently bypassing abandoned-run detection, busy-tree locking, and chain lookup.
614
+ - Fixed diagnostics to report the run folder actually used by the runner and to continue reporting remaining checks when a project marker is unreadable.
615
+ - 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.