Repository navigation
fix(update): keep a history-preflight refusal from aborting the update (#4718) #4746
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
Changes from 1 commit
fb2ce7d
52ec0a5
ac87703
f88191d
6a2b148
858e15e
c31b440
8a1b010
17d5c5e
c7924b8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -481,6 +481,16 @@ export async function runUpdate(): Promise<void> { | |
| " After the update: close the Codex app, run 'ocx doctor', then run 'ocx stop' once to retry.", | ||
| ); | ||
| } | ||
| if (decision.reason === "history-deferred") { | ||
| // Not the same warning: nothing was restored here. Saying "history metadata is | ||
| // incomplete" would imply config and catalog came back, and an operator who | ||
| // believed that would not know a teardown is still owed. | ||
| console.warn( | ||
| "⚠️ The shared teardown was refused by the Codex history preflight and restored nothing.\n" + | ||
| " Config, catalog, history and provenance were preserved, and the teardown receipt was kept.\n" + | ||
| " The proxy is down, so the update continues; close the Codex app and run 'ocx stop' once afterwards to finish the restore.", | ||
|
Comment on lines
+489
to
+491
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
For the reported AGENTS.md reference: src/AGENTS.md:L29-L29 Useful? React with 👍 / 👎.
Comment on lines
+489
to
+491
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win 🔎 Supported by static analysis🤖 get_repo_knowledge executed:
Length of output: 19091 🏁 Script executed: #!/bin/bash
set -u
printf '%s\n' '--- tracked files relevant to docs and updater ---'
git ls-files | rg '(^|/)(docs-site|src/update)(/|$)|(^|/)(README|CHANGELOG|CONTRIBUTING)' | head -200
printf '%s\n' '--- documentation references ---'
rg -n -i --glob '!node_modules' --glob '!dist' 'history-deferred|deferred teardown|teardown receipt|ocx stop|shared teardown|Codex history preflight|run .ocx stop' docs-site src README.md 2>/dev/null | head -300
printf '%s\n' '--- concise diff summary ---'
git diff --statRepository: lidge-jun/opencodex Length of output: 50375 🏁 Script executed: set -u
rg -n -i --glob '!node_modules' --glob '!dist' 'history-deferred|deferred teardown|teardown receipt|ocx stop|shared teardown|Codex history preflight|run .ocx stop' docs-site src README.md 2>/dev/null | head -300
git diff --statRepository: lidge-jun/opencodex Length of output: 42026 🏁 Script executed: set -u
printf '%s\n' '--- canonical lifecycle documentation ---'
sed -n '1,90p' docs-site/src/content/docs/reference/cli/lifecycle.md
printf '%s\n' '--- update documentation references ---'
rg -n -i 'ocx update|update|history|restore|teardown' docs-site/src/content/docs/reference/cli docs-site/src/content/docs/getting-started docs-site/src/content/docs/guides/codex-integration.md | head -160
printf '%s\n' '--- source branch and contract ---'
sed -n '450,500p' src/update/index.ts
sed -n '1,55p' src/update/stop-decision.mjsRepository: lidge-jun/opencodex Length of output: 35542 🏁 Script executed: sed -n '607,632p' docs-site/src/content/docs/reference/cli/lifecycle.mdRepository: lidge-jun/opencodex Length of output: 1592 Document the 🧰 Tools🪛 ast-grep (0.45.3)[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec. (detect-child-process-typescript) 🤖 Prompt for AI AgentsSource: Coding guidelines |
||
| ); | ||
|
Comment on lines
+488
to
+492
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win Run the required validation commands before merge. This change spans multiple 🧰 Tools🪛 ast-grep (0.45.3)[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec. (detect-child-process-typescript) 🤖 Prompt for AI AgentsSource: Coding guidelines |
||
| } | ||
| } | ||
|
|
||
| console.log(`Updating${latest ? ` to v${latest}` : ""}…\n$ ${bin} ${cmdArgs.join(" ")}`); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,3 @@ | ||
| /** Declaration for the plain-ESM stop contract shared with `bin/ocx.mjs`. */ | ||
| export declare const STOP_HISTORY_INCOMPLETE_EXIT_CODE: 79; | ||
| export declare const STOP_HISTORY_DEFERRED_EXIT_CODE: 80; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,4 @@ | ||
| import { STOP_HISTORY_INCOMPLETE_EXIT_CODE } from "./stop-contract.mjs"; | ||
| import { STOP_HISTORY_DEFERRED_EXIT_CODE, STOP_HISTORY_INCOMPLETE_EXIT_CODE } from "./stop-contract.mjs"; | ||
|
|
||
| /** | ||
| * May an update replace package files after `ocx stop` returned? | ||
|
|
@@ -22,13 +22,22 @@ import { STOP_HISTORY_INCOMPLETE_EXIT_CODE } from "./stop-contract.mjs"; | |
| * absence, and replacing files under a live server leaves it running a mix of old and | ||
| * new modules. | ||
| * - `ok` / `history-only` — proceed; the second also prints the manifest warning. | ||
| * - `history-deferred` — proceed; the stop is down but restored nothing, because the | ||
| * Codex history preflight refused first (#4718). The receipts it kept are the ONLY | ||
| * obligations it left, which the child proved before choosing this status, so | ||
| * `teardownOutstanding` seeing them is expected rather than disqualifying. Every other | ||
| * gate still applies: runtime records and a live or unreadable endpoint abort exactly | ||
| * as they do for a clean stop, because package replacement under a live server is the | ||
| * danger this function exists to prevent, and a history refusal says nothing about it. | ||
| */ | ||
| export function decidePostStopUpdate({ status, hasRuntimeState, liveness, teardownOutstanding = false }) { | ||
| const historyOnly = status === STOP_HISTORY_INCOMPLETE_EXIT_CODE; | ||
| if (status !== 0 && !historyOnly) return { proceed: false, reason: "stop-failed" }; | ||
| const historyDeferred = status === STOP_HISTORY_DEFERRED_EXIT_CODE; | ||
| if (status !== 0 && !historyOnly && !historyDeferred) return { proceed: false, reason: "stop-failed" }; | ||
| if (hasRuntimeState) return { proceed: false, reason: "runtime-state" }; | ||
| if (teardownOutstanding) return { proceed: false, reason: "teardown-outstanding" }; | ||
| if (teardownOutstanding && !historyDeferred) return { proceed: false, reason: "teardown-outstanding" }; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When another Useful? React with 👍 / 👎.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift Preserve the retained receipt set through the update boundary.
Pass the retained nonce set through the process boundary. Acquire a shared teardown/update lock before the updater’s final exact-set validation, make receipt claims honor that lock, revalidate the set while holding it, and keep the lock through package replacement. Abort if the set contains any nonce other than the retained set. Locking only the individual claim or scan is insufficient because it leaves a gap before package replacement. 🤖 Prompt for AI Agents |
||
| if (liveness === "live") return { proceed: false, reason: "proxy-live" }; | ||
| if (liveness !== "dead") return { proceed: false, reason: "proxy-unknown" }; | ||
| if (historyDeferred) return { proceed: true, reason: "history-deferred" }; | ||
|
Comment on lines
+35
to
+41
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This introduces a new cross-process exit-code contract and changes when outstanding teardown receipts permit package replacement across the owned AGENTS.md reference: src/AGENTS.md:L11-L11 Useful? React with 👍 / 👎. |
||
| return { proceed: true, reason: historyOnly ? "history-only" : "ok" }; | ||
| } | ||
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.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Do not return deferred status without a retained receipt.
claimTeardown()catchesclaimPendingTeardown()failures and leavesteardownNonceundefined. IfstopProxy()then uses its hard-kill fallback, it returnsfalsewithout settingstopFailed, sohandleStop()can still callrestoreSharedClientStateAfterStop().When that restore returns a structured
historyDeferredrefusal, and no inherited receipt was recovered, line 1163 assignshistoryDeferredNonces = []. If no obligation files exist,pendingTeardownsAreExactly([])returnstrue; lines 1226-1229 then return exit code80while no receipt records the refused teardown. The updater may replace package files, and later stops or updates cannot recover or gate on that teardown.Set
stopFailedwhenhistoryDeferredhas no retained nonce:Proposed fix
📝 Committable suggestion
🤖 Prompt for AI Agents