Describe the bug
The interrupted-upgrade sentinel (internal/packages/sentinel.go) has a broken lifecycle in both directions:
-
Deleted even when restore fails. internal/cli/upgrade.go:71-77's deferred cleanup calls RemoveSentinel() unconditionally once armed, regardless of whether the subsequent Restore call (line 238) actually succeeded — restore failures are only printed as warnings, not returned as hard errors, so the function reaches its normal return with the sentinel still armed-but-about-to-be-deleted. A failed migration still deletes the "resume breadcrumb" the sentinel exists to provide.
-
Never cleaned up after a successful manual restore. nodeup packages restore --from <path> — the exact command warnInterruptedUpgrade tells the user to run — never calls RemoveSentinel() on success (internal/cli/packages.go's runRestore). A user who follows the printed instructions and successfully restores their packages keeps seeing the "interrupted upgrade" warning on every subsequent nodeup command, indefinitely, because nothing ever clears the sentinel file.
Additional doc-mismatch in the same area
CHANGELOG says the sentinel "prompts the user to replay the package migration." What's actually implemented (internal/cli/root.go:21-40) is two fmt.Fprintf(os.Stderr, ...) lines with no interactive prompt and no automatic replay — the user must manually type the suggested command. nodeup upgrade itself never checks for a leftover sentinel from a previous crashed run before starting a new upgrade (it only manages its own sentinel). Consider either implementing the actual prompt-to-replay UX or correcting the CHANGELOG wording to match what ships.
Severity
High — the sentinel's entire purpose (helping a user recover from an interrupted upgrade) is undermined by both ends of its lifecycle being wrong.
Describe the bug
The interrupted-upgrade sentinel (
internal/packages/sentinel.go) has a broken lifecycle in both directions:Deleted even when restore fails.
internal/cli/upgrade.go:71-77's deferred cleanup callsRemoveSentinel()unconditionally once armed, regardless of whether the subsequentRestorecall (line 238) actually succeeded — restore failures are only printed as warnings, not returned as hard errors, so the function reaches its normal return with the sentinel still armed-but-about-to-be-deleted. A failed migration still deletes the "resume breadcrumb" the sentinel exists to provide.Never cleaned up after a successful manual restore.
nodeup packages restore --from <path>— the exact commandwarnInterruptedUpgradetells the user to run — never callsRemoveSentinel()on success (internal/cli/packages.go'srunRestore). A user who follows the printed instructions and successfully restores their packages keeps seeing the "interrupted upgrade" warning on every subsequentnodeupcommand, indefinitely, because nothing ever clears the sentinel file.Additional doc-mismatch in the same area
CHANGELOG says the sentinel "prompts the user to replay the package migration." What's actually implemented (
internal/cli/root.go:21-40) is twofmt.Fprintf(os.Stderr, ...)lines with no interactive prompt and no automatic replay — the user must manually type the suggested command.nodeup upgradeitself never checks for a leftover sentinel from a previous crashed run before starting a new upgrade (it only manages its own sentinel). Consider either implementing the actual prompt-to-replay UX or correcting the CHANGELOG wording to match what ships.Severity
High — the sentinel's entire purpose (helping a user recover from an interrupted upgrade) is undermined by both ends of its lifecycle being wrong.