Repository navigation
fix(chromium): keep existing engine until new one is verified (fixes #910) - #1386
choksi2212 wants to merge 3 commits into
Conversation
The subcommand list this gate hands to the deep-link router is pinned against the set createBossCLI() actually registers. Adding a new subcommand to BossCommand without also adding it here trips the gate with a single-line error pointing at the missing entry, which is the exact failure that took out the test runs on the boss-* CLI suite. Co-Authored-By: Claude <noreply@anthropic.com>
Reopening risa-labs-inc#910: the reinstall path deletes the working engine before extracting the new one, so a failed extraction leaves the host with neither engine. The macOS ditto fallback also silently produces a non-booting engine when its flags do not preserve the resources. Extract to a sibling directory, verify the new engine, then swap. Fixes risa-labs-inc#910.
|
Thanks @choksi2212 for the Chromium integrity fix and regression coverage, and for preserving the Claude co-author attribution on the CLI commit. Review result: unresolved - not ready to merge into current Against current
There are two blockers to preparation for merge:
Please recreate/rebase the minimal Chromium-only change from current
All reported PR checks are green, but they are checks for the stale |
|
Following up on kshivang's review for #910. Kshivang flagged the PR as a true duplicate of #929/#942 with #946 as the dev-target revision, and asked for the direct swap failure contract to be fully pinned (rollback, hard-kill recovery, mid-swap failure). What was changed in this branch:
Coordination:
Pending:
No AI/Claude attribution in any commit. |
08da448 to
4077231
Compare
|
@claude please preliminary triage-review #1386 at exact head 589c610. Read only the authors intended changes, compare current dev for supersession, and flag correctness, test, and security gaps. This stale branch is not merge-ready: full gate only after clean rebase. Give concrete blockers or say viable for rebase. |
|
Claude finished @fluck-boss's task in 2m 49s —— View job Preliminary triage-review of #1386 @
|
| #910 defect | PR @ 589c610 |
current dev |
|---|---|---|
| destructive pre-delete of the live engine | atomicInstallSwap + boss-chromium.swap / .old |
atomicEngineSwap, deriving .new / .old from targetDir, restoring on extract, promote and verification failure |
macOS ditto silently falling back to extractWithJava |
throws | throws, same behaviour (message names the symlink reason) |
| hard kill mid-swap leaves no engine | not handled | recoverInterruptedEngineSwap, called from promotePendingInstall before anything consults isChromiumInstalled |
| crashed extraction leaves an orphan sibling | not handled | same startup recovery discards boss-chromium.new |
| hashless archive is extracted and executed | unchanged - logs "No checksum available for engine archive" and proceeds |
EngineArchiveIntegrityVet.requirePinnedHash refuses before the fetch, fail closed |
dev also carries the regression tests this PR's are the weaker version of: a failed extraction restores the previous engine instead of leaving none, a failed verification rolls the swap back to the previous engine, promotePendingInstall restores the engine from an interrupted-swap backup when the target is missing, promotePendingInstall reclaims a crashed extraction sibling before staging work.
Correctness gaps in this head (were it the canonical line)
- No startup recovery for
boss-chromium.old-ChromiumAutoDownloader.kt,atomicInstallSwap. Iftarget.renameTo(backup)succeeds and both following renames fail, or the process dies in that window, the engine directory does not exist and the only copy sits in.old.promotePendingInstallat this head never looks at it. kshivang's blocker 2, still open. - The
.swapdirectory is never cleaned up on failure. The candidate loop'scatchonly recordslastError; the onlydeleteRecursively()runs at the start of the next attempt. A partial extract leaves a full engine tree (the ~160 MB figure AGENTS.md uses) in~/.boss/boss-chromium.swap, and if the process dies nothing ever reclaims it. - Dead branch:
atomicInstallSwapdeletesSTAGED_COMPLETE_MARKERfrom the swap dir, but that marker is only written whenstaged, and the staged path never calls this function. swapDir/backupDirdefault independently oftargetDir. They are siblings only by convention, andFiles.createDirectories(targetDir.parent)is the only parent created - a caller passing a differenttargetDirextracts into a directory whose parent may not exist.devderives both fromtargetDir, so they cannot desynchronise.
Test gaps
extraction failure preserves the existing enginehas a trivially-true assertion.extract = { _, _ -> }never creates the swap directory, soassertFalse(swap.exists())passes without exercising anything - and would keep passing with gap 2 present. A corrupted archive produces a partial tree; write one in the fake and the assertion fails. Fix this →atomicInstallSwap discards stale swap directorydoes not test its own name. It creates a staleboss-chromium.swap, then callsatomicInstallSwap(swap = freshSwap, ...)with a different directory and never asserts the stale one was removed. The stale cleanup lives ininstallFromCandidates, not inatomicInstallSwap, so this test structurally cannot cover it; what it does assert duplicates the happy-path test above it.- The
dittotest never reaches thedittoseam. It injectsextract = { throw ... }, so it checksinstallFromCandidatespropagation. Restoring theextractWithJavafallback insideextractWithDittowould fail no test here. (devhas the same limitation - noting it so the coverage is not claimed, not as a reason to preferdev.) - No mid-swap promotion-failure / rollback test, because
File.renameTois not injectable inatomicInstallSwap. kshivang's blocker 2 again.
Security
- Do not let a rebase reintroduce the hashless path. At this head an engine archive with
sha256 == nullis logged and extracted, and the binary is then executed. The atomic swap arguably makes that worse: previously a failing install left nothing behind, now an unverified engine lands reliably.devclosed this withEngineArchiveIntegrityVet. executable.nameexisting is a corruption check, not authenticity. Same ondev; just do not describe it as verifying the engine.dittostdout is interpolated verbatim into the exception message, which becomes the user-facingDownloadProgress.errorand carries local filesystem paths. Low severity, unchanged ondev.
Process
The 373-line detekt-baseline.xml rewrite is inside commit 589c610 itself, not a separate commit, so a cherry-pick cannot drop it - a rebase would have to re-extract the Chromium hunks by hand. Combined with the supersession above, that work has no payoff.
Recommendation: close #1386 (and #929/#942 if still open) as superseded by the merged dev implementation, crediting @choksi2212 on the issue. If anything is still wanted from this line of work, it is a new, dev-based PR for one thing only: making the extractWithDitto failure seam injectable so the no-Java-fallback rule is pinned by a test rather than by a comment.
· branch fix/issue-910-chromium-reinstall
|
Preliminary triage on head 589c610: I recommend closing this PR as superseded, not rebasing it. Current dev already has the non-destructive engine swap and refuses the macOS Java fallback, plus startup recovery and archive hash validation that this stale head lacks. The old head would risk reintroducing those gaps, and its detekt-baseline churn is unrelated. Claude found the same result in the review above. Closing is Kumar’s call; I am leaving the PR open. Thanks @choksi2212 for identifying and working through the original defect. |
|
Closing as superseded. Current dev already has the non-destructive Chromium engine swap, startup recovery, and archive hash verification; replaying this older branch could lose later protections. See the triage above for the comparison. Thanks for the original fix and tests. |
Reopened #910 - this was closed without a fix PR.
Two integrity defects in
ChromiumAutoDownloader.installFromCandidates:Destructive pre-delete. The non-staged install path deleted the existing engine directory before extracting, so a failed extraction left the host with neither engine. Extracted to a sibling swap directory instead, verified the new engine, then atomic-swapped via a backup dir; the previous engine is restored from the backup on any rename failure.
macOS
dittofallback. On macOS, when ditto failed the installer fell back toextractWithJava, which materialises symlink entries as text files. AChromium Framework.framework/Versions/Currentthat should be a symlink became a literal text file, and the engine booted silently broken. ditto failures now surface as install failures instead of silently degrading.Both staged (
pending) and non-staged (swap) paths now share the same shape: extract, verify, swap. The staged path keeps usingpromotePendingInstallon startup with its own backup dir; non-staged uses the newatomicInstallSwapat install time.Tests cover both scenarios:
extraction failure preserves the existing engineditto extraction failure is surfaced, not silently recoverednon-staged install swaps from a sibling directory and removes the swapatomicInstallSwap discards stale swap directoryFixes #910.