Skip to content

fix: self update --status and --recover refuse another spelling of the same OCM_HOME or HOME - #901

Merged
shakkernerd merged 3 commits into
mainfrom
selfupdate-receipt-home-alias
Oct 31, 2026
Merged

shakkernerd merged 3 commits into
mainfrom
selfupdate-receipt-home-alias

Conversation

@shakkernerd

@shakkernerd shakkernerd commented Oct 31, 2026 •

Copy link
Copy Markdown
Member

Fixes #899

What Problem This Solves

Fixes: ocm self update --status and ocm self update --recover refuse an update when OCM_HOME or HOME is reached through a different spelling of the same directory than the one the update was started with.

ocm self update records the OCM_HOME and HOME it ran with in the update receipt next to the installed binary. Before --status or --recover does anything, it checks that the current store and HOME are the ones in the receipt. That check compared the two paths as text, so a symlinked store, a symlinked HOME, /tmp versus /private/tmp on macOS, or the absolute path of an OCM_HOME that was relative when the update started all looked like a different store. OCM then printed self-update belongs to a different HOME or OCM_HOME; use the original store and stopped.

The case that hurts is an interrupted update. The installed binary has already been swapped for the candidate, the receipt says applying, and the update tells the user to run --recover. From another spelling of the same store, --recover was refused until the user found the exact original spelling, which the error does not show. See #899 for the full report.

User Impact

User impact: an update started through one spelling of the store can be inspected with --status and rolled back with --recover through any other spelling of the same store or HOME.

What stays the same:

  • A store or HOME that is a different directory, missing, or cannot be resolved is still refused with the same message, and the refused --recover changes nothing: the receipt stays applying and the candidate stays installed.
  • The original spelling works as before. The receipt keeps the spelling it recorded; recovering through an alias does not rewrite it, and the recovery worker runs with the recorded OCM_HOME and HOME.
  • ocm self update --check and starting a new update do not read this check and are unchanged.
  • No receipt format, CLI flag or JSON output changes.

Why This Change Was Made

#892 fixed the same class of problem for service and supervisor ownership by adding same_ocm_home in src/store/layout.rs: a recorded path names the current one when the text matches, or when both are absolute and resolve to the same existing directory. A relative, missing or unresolvable recorded path never matches another spelling. The self-update receipt check was not part of that change (#881 mentioned it as a side note).

The change has two parts in src/cli/self_update_transaction.rs:

  • validate_receipt_home uses that same rule for both the recorded OCM_HOME and the recorded HOME, instead of !=. Using the existing function keeps one definition of "same store" for services, the supervisor and self-update, and inherits its refusals for missing and unresolvable paths. The receipt stores paths as JSON strings, so they are always valid UTF-8; a current path that is not UTF-8 falls back to the previous exact comparison.
  • The detached worker that performs recovery is always the retained previous binary, which after an upgrade is the older release. Older releases still compare the receipt's directories as text. If the worker inherited the caller's alias spelling, a recovery started from the new binary would pass the new check, mark the receipt rollingBack, and then be refused by the old worker before anything was rolled back. spawn_worker now starts the worker with the OCM_HOME and HOME the receipt recorded, so a worker of any version sees the exact spelling it expects. These are the same directories the caller was validated against, and the worker already switched to the recorded OCM_HOME before doing any work, so the store it acts on is unchanged.

Evidence

Before the fix, on main at f67a89d, a scripted run of the real CLI (isolated HOME, OCM_HOME and TMPDIR, stubbed service managers, a loopback release fixture with a fake 9.9.9 candidate, a private copy of the binary) reproduced the report on Linux x86_64 and macOS arm64. With the original spelling, --status succeeded and --recover of an interrupted update rolled it back. Every alias was refused with the message above: a symlinked store, a symlinked HOME with an explicit OCM_HOME, a symlinked HOME with the default store, the absolute symlink path of a relative OCM_HOME, /tmp for a /private/tmp store (macOS only), and --status and --recover of an interrupted (applying) update through a symlinked store. After the refused --recover, the receipt was still applying and the candidate was still installed.

After the fix, the same script on this branch, on both platforms:

Case Before After
Original spelling --status (absolute, relative, default store) accepted accepted
Symlinked store, --status refused accepted
Symlinked HOME (explicit OCM_HOME, and default store), --status refused accepted
Absolute symlink alias of a relative OCM_HOME, --status refused accepted
/tmp spelling of a /private/tmp store, --status (macOS) refused accepted
Interrupted update, --status through a symlinked store refused accepted, shows applying
Interrupted update, --recover through a symlinked store refused, still applying rolledBack, original binary restored, receipt keeps its recorded ocmHome
Different existing store or HOME, --status and --recover refused refused, no effect
Missing store, --status and --recover refused refused, no effect
--check works works

Regression test. local_transaction_survives_gateway_teardown_and_recovers_failures in tests/self_update_transaction_tests.rs already interrupts an update started with a relative OCM_HOME and checks that a missing HOME or OCM_HOME is refused. It now also:

  • refuses a different existing directory for each of them, with no change to the receipt or services;
  • runs --status through a symlinked store, the absolute path of the relative OCM_HOME, and a symlinked HOME, each showing the same applying update without touching services;
  • performs the recovery itself through a symlinked store and HOME, checks that the rollback worker's service calls used the recorded HOME rather than the alias, and then checks the existing assertions: rolledBack, same receipt id, recorded ocmHome unchanged, original binary restored, no nested store created, and a replayed --recover changes nothing.

This test passed on this branch on Linux (320 s) and macOS (241 s). Against the unchanged source it fails on the first alias --status.

A unit test in src/cli/self_update_transaction.rs checks the worker's start command directly: given a caller whose OCM_HOME and HOME are other spellings, the worker gets the receipt's recorded values. The integration test can't catch a missing OCM_HOME override on its own, because a worker built from the same source accepts the alias and then switches to the recorded OCM_HOME itself. With that override removed, the unit test fails.

Recovery with an older retained worker. The test above builds both binaries from this branch, so it can't show what happens when the retained worker is an older release. To check that, a temporary, uncommitted variant of the same test builds the binary that starts the update, which becomes the retained worker, from main at f67a89d. Its receipt check is the same text comparison as in v0.3.8. The 9.9.9 candidate is built from this branch. On Linux:

  • With only the new check (first revision of this PR), the alias --recover left the receipt at rollingBack with the candidate still installed, because the older worker refused the alias.
  • With the worker handoff added, the same run rolled back and passed every assertion. This also shows the new binary reads a receipt written by the older one; the receipt format is unchanged.

Other checks on the exact commit: git diff --check, cargo check --workspace --all-targets --locked (Linux), and cargo fmt --check (macOS) passed, and the scripted runs above were repeated on this revision on both platforms with the same results. Hosted CI runs the full suite, including Linux and macOS.

Limits. The scripted runs used a fake candidate binary and no installed OCM service, so they don't exercise service restarts through an alias. The integration test does cover recovery with its stand-in service manager. The older-worker check used main's source rather than the published v0.3.8 binary; their receipt check and worker launch are the same. Windows does not support recoverable self-update and was not run.

@clawsweeper

clawsweeper Bot commented Oct 31, 2026 •

Copy link
Copy Markdown

🦞👀
ClawSweeper picked this up.

Pull request received. I will update this pull request when review starts.

ClawSweeper review complete

ClawSweeper finished reviewing this revision. The review result is being finalized.

View the workflow run.

@clawsweeper clawsweeper Bot added clawsweeper:intake:bug Preliminary submission classification; full source review pending. clawsweeper:intake:needs-environment Preliminary submission classification; full source review pending. P2 Normal priority bug or improvement with limited blast radius. merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. and removed clawsweeper:intake:bug Preliminary submission classification; full source review pending. clawsweeper:intake:needs-environment Preliminary submission classification; full source review pending. labels Oct 31, 2026
@clawsweeper

clawsweeper Bot commented Oct 31, 2026 •

Copy link
Copy Markdown

Codex review: needs maintainer review before merge.

What this changes

This PR accepts equivalent HOME and OCM_HOME paths for self-update status and recovery and gives retained workers the recorded spellings.

Example: An interrupted update records /tmp/ocm-repro/store, while /tmp/ocm-repro/store-link points to that directory.

  • Before: Recovery through store-link refuses with “different HOME or OCM_HOME” and leaves the update applying.
  • After: Recovery accepts the alias and starts the retained worker with the recorded paths to restore the original binary.

Review scores

Measure Result What it means
Overall readiness 🦐 gold shrimp (3/6) The focused patch and compatibility reasoning are sound; execution confidence rests on reported CLI results and inspected regression coverage.
Proof confidence 🦐 gold shrimp (3/6) This maintainer-authored PR is outside the ordinary contributor proof gate; its body reports isolated Linux and macOS CLI alias recovery, foreign-directory no-effect controls and mixed-version worker recovery, although independently inspectable after-fix traces are absent.
Patch quality 🦞 diamond lobster (5/6) No actionable review findings were identified.

Product

Kind: Bug fix · Worth it: Yes · Fix scope: Complete
User problem: Users can be refused status or recovery for their own update when another shell reaches the same directories through aliases.
Reason: This maintainer-authored repair restores useful recovery behavior while retaining foreign-directory refusals and the existing receipt contract.

Merge readiness

✅ Ready for maintainer review

Keep open: this maintainer-authored PR fixes a recovery defect still present on main, and no blocking patch findings remain.

Priority: P2
Reviewed head: 1dd8f6c58cfe7a4dc36d2b2080d12bb7e4f669aa

Before merge

None.

Findings

None.

Tests

  • Missing end-to-end proof: The reported real-CLI scenarios cover the changed paths, but raw after-fix terminal traces are not supplied for independent inspection.
Agent review details

How this fits together

The self-update transaction layer validates the caller against a private receipt beside the installed executable, then delegates rollback and service restoration to the retained previous executable.

flowchart TD
  A[Status or recovery command] --> B[Read private receipt]
  B --> C[Compare recorded and current paths]
  C --> D[Reject different directories]
  C --> E[Return update status]
  C --> F[Start retained worker with recorded paths]
  F --> G[Restore executable and services]
Loading

Technical review

Best possible solution:

Use the shared directory-identity rule while preserving recorded receipt paths for older recovery workers.

Do we have a high-confidence way to reproduce the issue?

Current main directly compares receipt paths, and #899 supplies concrete Linux and macOS reproductions; this review executed no target code.

Is this the best way to solve the issue?

The patch addresses the comparison at its existing owner and preserves older-worker compatibility without changing path resolution globally or introducing another identity mechanism.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning medium; reviewed against 88185eee3cfa.

Provenance checked

  • Receipt ownership comparison changes intended behavior with a stated reason (8d9242e: The original transaction isolated recovery to the initiating store and HOME before executable or service changes.)
  • Detached worker environment keeps the original intent (8d9242e: The retained initiating executable owns update and rollback under the initiating context; the later relative-store repair preserved that context when changing directories.)

Testing

Proof path: shipped entry point.

Security

None.

Evidence

What I checked:

Likely related people:

  • Shakker: Raw commit e81d895 adds src/cli/self_update_transaction.rs:359 relative to its recorded parents. This identifies author metadata, not feature responsibility or a PR merger. (role: source-line author; confidence: high; commits: e81d895b853d; files: src/cli/self_update_transaction.rs)
  • Jason (Json): Raw commit 8d9242e adds src/cli/self_update_transaction.rs:378 relative to its recorded parents. This identifies author metadata, not feature responsibility or a PR merger. (role: source-line author; confidence: high; commits: 8d9242ecbcff; files: src/cli/self_update_transaction.rs)

Labels

Label changes:

  • add rating: 🦐 gold shrimp: Overall readiness is 🦐 gold shrimp; proof is 🦐 gold shrimp and patch quality is 🦞 diamond lobster.
  • remove rating: 🐚 platinum hermit: Current PR rating is rating: 🦐 gold shrimp, so this older rating label is no longer current.

Label justifications:

  • P2: Equivalent path spellings can block self-update inspection and interrupted-update recovery in an advanced CLI workflow; the original spelling remains usable.
  • rating: 🦐 gold shrimp: Overall readiness is 🦐 gold shrimp; proof is 🦐 gold shrimp and patch quality is 🦞 diamond lobster.
  • status: 👀 ready for maintainer look: ClawSweeper has no concrete contributor-facing blocker left for this PR.

Rating scale

6/6 🦀 challenger crab · 5/6 🦞 diamond lobster · 4/6 🐚 platinum hermit · 3/6 🦐 gold shrimp · 2/6 🦪 silver shellfish · 1/6 🧂 unranked krab. Overall follows the weaker of proof and patch quality; ✨ marks media proof (a screenshot, video, or linked artifact) that directly shows the changed behavior.

Workflow

ClawSweeper edits this one comment on every review. Comment @clawsweeper re-review for a fresh review only. Repair and merge execution lanes are retired.

History

Review history (2 earlier review cycles)
  • reviewed 2026-10-31T03:03:09.938Z sha 18ff1e5 :: blocked before merge. :: [P2] Pass recorded directories to the retained recovery worker
  • reviewed 2026-10-31T03:59:27.411Z sha 515f6b0 :: needs maintainer review before merge. :: none

Reviewed October 31, 2026, 12:31 AM ET / 04:31 UTC (Revision 3).

@shakkernerd

Copy link
Copy Markdown
Member Author

Fixed in 515f6b0: the recovery worker now gets the OCM_HOME and HOME the receipt recorded, instead of the caller's spelling. The worker is always the retained previous binary, which after an upgrade is the older release, and older releases compare those directories as text. With the recorded spellings, a worker of any version accepts the receipt, and it acts on the same store, because the caller was already validated against those directories and the worker already switched to the recorded OCM_HOME before doing anything.

I reproduced the failure before fixing it. A temporary variant of local_transaction_survives_gateway_teardown_and_recovers_failures built the binary that starts the update, and so becomes the retained worker, from main at f67a89d, whose receipt check matches v0.3.8. The 9.9.9 candidate was built from this branch. On 18ff1e5, the alias --recover left the receipt at rollingBack with the candidate still installed. On 515f6b0 the same run rolls back and passes every assertion. That run also shows the new binary reading a receipt written by the older one; the receipt format did not change.

The committed test now also checks that the rollback worker's service calls use the recorded HOME rather than the symlinked one. With only the two new spawn_worker lines removed, it fails on that check. The PR body's Evidence section has the details.

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Oct 31, 2026 •

Copy link
Copy Markdown

🦞👀
Exact review queued.

Re-review progress:

@clawsweeper clawsweeper Bot added clawsweeper:intake:bug Preliminary submission classification; full source review pending. clawsweeper:intake:needs-repro Preliminary submission classification; full source review pending. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. clawsweeper:intake:bug Preliminary submission classification; full source review pending. clawsweeper:intake:needs-repro Preliminary submission classification; full source review pending. labels Oct 31, 2026
@shakkernerd

Copy link
Copy Markdown
Member Author

1dd8f6c adds test coverage for the other half of the worker handoff and changes no behavior. The integration test couldn't catch a missing OCM_HOME override: a worker built from the same source accepts the alias and then switches to the recorded OCM_HOME itself, so only an older worker would notice. The worker's start command is now built in worker_process, with the same calls in the same order, and a unit test checks that a caller with other spellings of OCM_HOME and HOME starts the worker with the receipt's recorded values. With the OCM_HOME override removed, that test fails. The alias scripted run and the integration test still pass on this revision.

@clawsweeper re-review

@clawsweeper

clawsweeper Bot commented Oct 31, 2026 •

Copy link
Copy Markdown

🦞👀
Exact review queued.

Re-review progress:

@clawsweeper clawsweeper Bot added clawsweeper:intake:bug Preliminary submission classification; full source review pending. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. and removed rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. labels Oct 31, 2026
@clawsweeper clawsweeper Bot removed the clawsweeper:intake:bug Preliminary submission classification; full source review pending. label Oct 31, 2026
@shakkernerd
shakkernerd merged commit f560843 into main Oct 31, 2026
14 checks passed
@shakkernerd
shakkernerd deleted the selfupdate-receipt-home-alias branch October 31, 2026 04:54
@shakkernerd

Copy link
Copy Markdown
Member Author

Fixed: an interrupted or finished self-update can now be inspected and recovered through any spelling of the same OCM_HOME or HOME, while a different store is still refused.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P2 Normal priority bug or improvement with limited blast radius. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A self-update started through one spelling of OCM_HOME cannot be inspected or recovered through another spelling of the same store

1 participant