-
Notifications
You must be signed in to change notification settings - Fork 1.1k
fix(stop): name the real refusal cause instead of asserting ownership #4237
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
87 changes: 87 additions & 0 deletions
87
devlog/_plan/260911_l4_service_cli/020_wp2_stop_refusal.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| # wp2 — #4169: every stop refusal is reported as a CODEX_HOME ownership mismatch | ||
|
|
||
| Work-phase 2 of the L4 lane, stacked on wp1. Carried source: PR #4170 by `yeongjunyoo`, | ||
| head `4d72ef0103`, three commits on base `c15a98caa9`. All four of its files are inside this | ||
| lane's ownership, so nothing was dropped. | ||
|
|
||
| ## What the carry does | ||
|
|
||
| `POST /api/stop` refuses for three distinct reasons. `stopProxy` preferred the server's own | ||
| message but fell back, when none was readable, to one hardcoded sentence naming a fourth cause | ||
| the server never reports. #4170 captures the refusal `code`, selects the fallback wording from | ||
| it, and returns the refusal per attempt instead of publishing it to module state — the last of | ||
| those fixes a real interleaving bug where two overlapping stops could swap causes. | ||
|
|
||
| ## What the carry left open | ||
|
|
||
| #4169's **Expected** section asks for two things: | ||
|
|
||
| 1. a `respawnable_service` or `service_state_unknown` refusal must not be described as an | ||
| ownership mismatch, and | ||
| 2. *"the recommended next command should not be the command the operator just ran."* | ||
|
|
||
| The carry does (1). Subagent Heisenberg traced (2) and returned `LOOP_REAL`: | ||
|
|
||
| - `ocx stop` reaches `POST /api/stop` through `dispatch.stop` → `handleStop` → | ||
| `stopWithDeferral` → `stopProxy` → `stopProxyGracefully`. Stopping the service manager | ||
| first does not skip it. | ||
| - The server answers with *"the stop must be run by `ocx stop`"* because that refusal is | ||
| written for an API client, and `management-api.ts` emits `respawnable_service` precisely | ||
| when the CLI's teardown receipt was **not** honoured, so it cannot tell the two apart. | ||
| - `handleStop` prints `err.message` verbatim, so the operator is told to run the command they | ||
| are already running. | ||
| - The carried fallback for `respawnable_service` also ends in "Run `ocx stop`", making the | ||
| empty-body path a tighter loop than the one being fixed. | ||
|
|
||
| There is no header, flag, query parameter or route that marks a CLI-originated stop, so the | ||
| server cannot word the refusal differently. The correction belongs to the CLI, which is the one | ||
| caller that knows which it is. | ||
|
|
||
| ## What this work-phase adds | ||
|
|
||
| - The refusal `code` travels on `ProxyOwnershipRefusedError`. The reporting caller acts on the | ||
| cause; re-parsing the prose is not an option, because the prose is the server's. | ||
| - `refusalFallbackMessage` names the cause only. | ||
| - A new exported `refusalNextStep(code)` owns the command, and `handleStop` prints it under the | ||
| refusal at both call sites. The only callers of `stopProxy` are `ocx stop` and the service | ||
| manager's own cleanup, and both have already asked the service manager to stop by then, so no | ||
| branch answers with the command that just failed. | ||
|
|
||
| `ProxyOwnershipRefusedError` keeps its name. The issue suggests renaming it and the name does | ||
| overstate what it carries, but the carrying author deliberately deferred that as a separate | ||
| wider change and it is not part of the issue's Expected behaviour. Recorded, not decided | ||
| unilaterally. | ||
|
|
||
| ## Audit | ||
|
|
||
| Subagent Confucius reviewed the staged diff adversarially and returned `BLOCKERS_FOUND` with one | ||
| item: the new next-step test banned the literal `` `ocx stop` ``, which the production wording | ||
| contains **in order to rule it out**, so the test failed against its own implementation. Folded: | ||
| the assertion now bans a recommendation (`/Run \`ocx stop\`/`) rather than a mention. Its second | ||
| observation — that no test pinned the CLI wiring — is folded as a source oracle over the two | ||
| `refusalNextStep(err.code)` print sites. | ||
|
|
||
| Re-checked deterministically afterwards: the four source-oracle counts in | ||
| `grok-lifecycle.test.ts` are all exactly 2, and no `refusalNextStep` branch matches | ||
| `/Run \`ocx stop\`/`. | ||
|
|
||
| ## CI repair carried into this phase | ||
|
|
||
| Hosted CI on wp1's head failed on Linux and macOS with one test: | ||
| `codex-cli-update-launcher-policy` asserts `bin/ocx.mjs` contains | ||
| `"!codexCliUpdateInspection && isNodeModulesInstall()"` as an adjacent string. The carry gates | ||
| the #1849 boot probe on the npm layout, inserting `installMethod === "npm"` between those two | ||
| clauses. The invariant the oracle protects is intact and still evaluated first; only the | ||
| adjacency changed. The oracle now locates the guard wrapping the `bootRestoreProbe` call and | ||
| asserts both clauses are in it. | ||
|
|
||
| `tests/codex-integration/codex-cli-update-launcher-policy.test.ts` is not in the packet's | ||
| keep-set. It is an oracle over `bin/ocx.mjs`, which this lane owns, and the round already | ||
| granted L2 the same thing for the same reason: a lane that changes a file owns the oracle | ||
| asserting it, or the change cannot land at all. Reported rather than assumed. | ||
|
|
||
| ## Not run | ||
|
|
||
| `bun test`, `bun run test`, `bun run test:changed`, `bun run typecheck`, `bun run build:gui` | ||
| and `bun install` are NOT RUN by operator instruction. Hosted CI on the exact pushed head is | ||
| the only product evidence this round accepts. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When
POST /api/stopreturns its normalrespawnable_serviceorself_unload_servicebody,err.messageis printed immediately above this line, and those server messages explicitly say to runocx stop(src/server/management-api.ts:300and:314). Appending a second sentence that contradicts that recommendation still leaves the operator being told to rerun the command that just failed, so the intended loop remains. For these known refusal codes, replace or sanitize the API-oriented message before printing it rather than merely adding another next step; apply the same change to the orphan-recovery catch.Useful? React with 👍 / 👎.