Repository navigation
fix(runtime): keep local OpenCode auto-start reliable - #11
Merged
Merged
Conversation
Three ways the local server could stay down when the app came back: - Idle-stop race: the watchdog set its in-progress flag only inside the launched coroutine, so a foreground return in between saw Ready and skipped the restore; and a start that queued behind a running idle stop was torn down by its stopSelf(). IdleStopGate orders idle-stop decisions against incoming commands, the in-progress flag is set synchronously, the stop re-checks idleness under the manager lock, and a superseded stop keeps the service up. - Interrupted update: a Broken status left by an interrupted activation never reached startLocked(), where that recovery lives; the foreground gate now restores it when a rollback copy is present. - Port: this fork moves to 4098 (existing installs migrate from 4097), and an open port held by a server that does not accept our credentials is reported as "Port N is in use by another process" instead of Ready. The chat also shows a "Stopped manually" banner with a Start action when the user's own Stop is what keeps auto-start off.
…onflicts - the idle stop holds its own lease tag and the still-idle check ignores it; before, its own lease always read as active work, so it never stopped and retried every watchdog tick (idleStopStillApplies, tracker reset on decline) - finishIdleStop runs on the main thread so it cannot race onStartCommand - an open port is ours when our own proot is alive; only otherwise probe - interrupted in-app updates (update/rollback journals) also allow restore - port-in-use reason goes through LocalRuntimeMessages, in all locales - fix LocalRuntimeStatus import, ktlint wrapping in the new tests
Codex's app-server and terminal sessions also run proot from the runtime directory, so any of them made a foreign server on the port look like ours. Match /usr/local/bin/opencode serve in the cmdline instead. Also reset the idle timer when a newer command supersedes an idle stop, and correct hasInterruptedActivation's KDoc.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes "the local OpenCode sometimes does not auto-start when opening the app" (cold start, after a long background stay, right after updating OpenCode).
Idle-stop race
stopSelf().IdleStopGate(generation/token) orders idle-stop decisions against incoming commands. The in-progress flag is now set synchronously.stopIfStillWanted), ignoring its own lease tag (idleStopStillApplies).finishIdleStopruns on the main thread, and a superseded stop keeps the service up. The idle timer resets on decline or supersede.Interrupted install/update
Brokenstatus left by an interrupted activation (environment.rollback) or in-app update (update-transaction.json/rollback-transaction.json) is now restored on foreground return. Previously it never reachedstartLocked(), where that recovery lives.Port
opencode serveproot is alive. Otherwise an authenticatedglobal/healthprobe decides.UI
Tests
IdleStopGateTest,IdleStopDecisionTest(incl. a real managerstopIfStillWanted),IdleStopSequenceTest,LocalRuntimePortTest,RuntimeAutoStartPolicyTest,LocalRuntimeProcessLauncherTest.Follow-ups (not in this PR)
--porttoo when deciding a survivingopencode serveowns the port.autoRestartEnabledwhen the superseding command did not set it.localRuntimeStoppedByUseras a StateFlow, so the banner updates even when the status itself does not change.ensureRunning/ updateUpToDatepaths too.Note: written without a local JDK; CI is the first compile.
Pre-PR review
Reviewed in three rounds by repo-reviewer:
LocalRuntimeStatusimport; the idle stop counting its own lease as active work (so it never stopped); a hardcoded English port-in-use reason; ktlint wrapping in the new tests.判定: APPROVE
ブロッカー
なし
提案(非ブロッキング)
LocalRuntimeProcessLauncher.kt:290-301 — ポート番号までは照合していません。
findOpenCodeServerPidsはopencode serveかどうかだけを見ています。そのため、次の稀なケースでは他人のサーバーを自分のものと判定します。opencode serveが生き残っている。startInstalledは 4098 の他人のサーバーを自分のものとみなし、HTTP probe を飛ばして Ready にします。ownsPort(port)から port を渡し、--portの直後の値がportと一致するかまで確かめると確実です(LocalRuntimeProcessLauncher.kt:66-67の引数順と同じです)。これも後続課題に入れて構いません。見送った 4 件(auto-restart を元に戻す件、StateFlow 化、ATOMIC_MOVE、コールドスタート時の ownership 判定)を PR 本文に書く方針で問題ありません。
未コンパイルのままなので、マージ前に CI の
detekt spotlessCheckとユニットテストが通ることを確認してください。チェック済み項目
findOpenCodeServerPids:findManagedRuntimeRootPidsの結果を絞り込み、cmdline を\u0000で分割して/usr/local/bin/opencodeの直後がserveのものだけを残しています。LocalRuntimeProcessLauncher.kt:57-68、prefix の後に-w /workspace /usr/local/bin/opencode serve --hostname … --port …)とこの判定が一致することを確認しました。opencode serveは proot の子プロセス側になり、親の cmdline には proot と runtimeDirectory の両方は現れません。そのため root の判定には引っかからず、誤って自分のものと判定されることもありません。runtimeDirectoryを参照しているので、正しく書けています(LocalRuntimeManager.kt:59)。getOrDefault(emptyList())で除外されます。[100]だけが返るので、本番の経路を検証できています。既存のfindManagedRuntimeRootPidsのテストへの影響はありません。idleTracker.reset(): 割り込まれた経路でもidleTrackerLockの中で呼ばれており、断ったときの経路と揃いました(LocalRuntimeService.kt:639-641)。割り込んだのが Stop の場合でも、startWatchdogは cancel される scope 上で何もしないので問題ありません。hasInterruptedActivationの説明が、environment.rollbackと journal 2 種の両方を扱う実装と一致しました(LocalRuntimeInstaller.kt:243-251)。private const val OPEN_CODE_SERVER_BINARYは detekt の命名規則を満たしています。