fix: trigger auto-update check on headless serve startup#940
Conversation
Auto-update was wired solely into the TUI bootstrap (`cli/cmd/tui/thread.ts` → `worker.checkUpgrade` → `upgrade()`). The headless `serve` command — how the VS Code / Cursor extension runs altimate-code — never checked for or installed updates, so the extension fleet froze at whatever version was installed at onboarding (e.g. `0.7.3`), regardless of the `autoupdate` setting. Mirror the TUI in `serve`: fire a single best-effort `upgrade()` check shortly after the server is listening, via the existing `bootstrap()` helper. All policy stays inside `upgrade()` — install-method detection, the `autoupdate` gate (`true` / `false` / `"notify"`), the downgrade guard, and telemetry — so this only adds the missing trigger. Fire-and-forget, detached from request handling, errors logged not thrown. Closes #939
📝 WalkthroughWalkthroughA new ChangesServe Startup Auto-Update
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Suggested labels
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Code Review —
|
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
packages/opencode/src/cli/cmd/serve.ts (1)
58-63: ⚡ Quick winExtract the delayed upgrade trigger and name the delay constant.
The inline
setTimeoutclosure plus raw1000makes this path harder to test and reason about. A small extraction (e.g.,triggerServeStartupUpgradeCheck) andSERVE_STARTUP_UPGRADE_DELAY_MSconstant would make intent explicit and allow focused unit coverage for “calls upgrade” + “never rejects”.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/opencode/src/cli/cmd/serve.ts` around lines 58 - 63, Extract the delayed upgrade check logic from the inline setTimeout closure into a separate function named triggerServeStartupUpgradeCheck and create a constant SERVE_STARTUP_UPGRADE_DELAY_MS set to 1000. Replace the current setTimeout block with a call to setTimeout that uses the constant and function name, making the intent clearer and improving testability of the upgrade check behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/opencode/src/cli/cmd/serve.ts`:
- Around line 59-61: The bootstrap() call on line 59 uses process.cwd() as the
instance key, which is the same bucket used by live serve requests. Since
bootstrap() calls Instance.dispose() in its finally block, this disposes the
active serve instance, causing lifecycle races. Fix this by passing an
isolated/unique instance key to the bootstrap() function instead of
process.cwd(), so the bootstrap upgrade check runs with its own separate
instance without interfering with the running server's instance lifecycle.
---
Nitpick comments:
In `@packages/opencode/src/cli/cmd/serve.ts`:
- Around line 58-63: Extract the delayed upgrade check logic from the inline
setTimeout closure into a separate function named
triggerServeStartupUpgradeCheck and create a constant
SERVE_STARTUP_UPGRADE_DELAY_MS set to 1000. Replace the current setTimeout block
with a call to setTimeout that uses the constant and function name, making the
intent clearer and improving testability of the upgrade check behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 620c7131-9228-4028-a46a-53c2a26e858e
📒 Files selected for processing (1)
packages/opencode/src/cli/cmd/serve.ts
There was a problem hiding this comment.
1 issue found across 1 file
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
Address review on #940: the startup upgrade check wrapped `upgrade()` in `bootstrap()`, whose `finally` calls `Instance.dispose()`. Running in-process with the HTTP server, that disposed the entire `process.cwd()` instance bucket — the same bucket server requests default to when no `directory` is supplied (`server/server.ts:196`) — tearing down state shared with in-flight requests (use-after-dispose / needless churn). The TUI path it mirrors is safe only because it runs in a separate worker thread. Fix: mirror the TUI worker faithfully — use raw `Instance.provide` on `process.cwd()` and, exactly like the worker, never dispose. This reuses/seeds the shared cached instance (Bus notifications still reach default-directory SSE subscribers) and removes the teardown entirely. `upgrade()` reads only global config + `Installation`; the instance context exists solely for `Bus.publish`. Also: extract the trigger into `serve-upgrade-check.ts` (named `STARTUP_UPGRADE_DELAY_MS`, `runStartupUpgradeCheck`, `scheduleStartupUpgradeCheck`) so it's unit-testable and self-documenting, and add tests asserting it runs `upgrade()` once in the `process.cwd()` instance and never rejects on failure.
|
Thanks — the MAJOR is a real bug, confirmed all three claims against the code ( On the fix — I went a slightly different route than the sentinel dir, and I think it's strictly better; flagging so you can sanity-check: The root issue isn't which directory we dispose — it's that we dispose at all while in-process with the server. The TUI path I was mirroring ( Why this over the sentinel
Confirmed MINOR / NIT / testability — all addressed: extracted the trigger into Verification note (re: "how did you confirm the fleet updates"): the serve-side trigger can't unstick the existing 0.7.3 fleet on its own — they won't have this code until they upgrade once (catch-22). That half is handled from the extension in AltimateAI/vscode-altimate-mcp-server#369, which version-checks and re-installs on activation. End-to-end, a stuck machine gets pulled forward by #369; #940 keeps every future serve self-updating. |
There was a problem hiding this comment.
Pull request overview
Adds a missing auto-update trigger to the headless altimate serve startup path so extension-launched servers perform the same best-effort upgrade check as the TUI, without blocking request handling.
Changes:
- Schedule a one-shot startup upgrade check shortly after
Server.listen()inserve. - Introduce
serve-upgrade-check.tsto runupgrade()inside aprocess.cwd()Instancecontext without disposing the shared instance bucket. - Add a Bun test validating orchestration and non-throwing behavior when
upgrade()fails.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| packages/opencode/src/cli/cmd/serve.ts | Triggers the startup auto-update check from the headless server entrypoint. |
| packages/opencode/src/cli/cmd/serve-upgrade-check.ts | Implements the delayed, best-effort upgrade check within a shared Instance context. |
| packages/opencode/test/cli/serve-upgrade-check.test.ts | Adds unit coverage for the new startup upgrade check helper. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // altimate_change start — self-update on headless serve startup | ||
| import { scheduleStartupUpgradeCheck } from "./serve-upgrade-check" | ||
| // altimate_change end |
…module
CI "TypeScript" job failed: `SyntaxError: Export named 'isValidVersion' not
found in module cli/upgrade.ts`. Cause was the new test — bun's `mock.module`
is process-global, so mocking `../upgrade` (and `../../project/instance`,
`../../project/bootstrap`) replaced those modules for the entire test run,
stripping `cli/upgrade`'s other exports (`compareVersions`, `isValidVersion`)
that `upgrade-decision.test.ts` imports. It passed locally only because the
file was run in isolation.
Replace module-mocking with dependency injection: `runStartupUpgradeCheck`
takes an optional `StartupUpgradeDeps` ({ provide, run }) defaulting to the real
`Instance.provide`(no-dispose) + `upgrade`. The test injects fakes directly — no
global module state touched. Added a case for `provide()` itself rejecting.
Verified in the combined suite (previously-colliding files together, then full
test/cli): 512 pass, 0 fail; typecheck clean.
…mment Follow-up to inline review on #940: - serve.ts: drop unused `Installation`, `Workspace`, `Project` imports (pre-existing dead code; Copilot flagged Installation, the other two are the same issue) — avoids future lint failures. - serve-upgrade-check.ts: pass the raw `Error` to `log.error` instead of `String(err)`, so `Log.formatError` emits the message + `cause` chain rather than a flattened string. - serve-upgrade-check.ts: correct the doc comment — the never-rejects guarantee comes from the explicit inner `.catch` wrapper (and outer try/catch), not from `upgrade()` swallowing its own errors.
|
Pushed fixes for the CI failure and the remaining review comments. CI red (TypeScript job) — fixed ( Copilot nits — fixed (
Dispose bug (MAJOR / coderabbit / cubic) — already resolved in |
PINEAPPLE
Summary
Triggers a best-effort auto-update check when headless
altimate servestarts, so extension-launched servers follow the same upgrade path as TUI.Updated implementation details to match the final code:
Instance.provide({ directory: process.cwd(), init: InstanceBootstrap, fn: ... })(nobootstrap()wrapper), avoiding unintendedInstance.dispose()teardown during in-process server runtime.upgrade()(autoupdate gating, install method handling, downgrade guard, telemetry).packages/opencode/src/cli/cmd/serve-upgrade-check.ts.Test Plan
process.cwd()instance usagepackages/opencode/test/clisuite passing (512 pass, 0 fail).mock.modulestate is resolved by DI-based test rewrite.Checklist