Problem
Restored OpenCode tabs can flash/reload repeatedly and never become usable. This appears to be an infinite restore retry loop around failed opencode terminals, not a normal reconnect burst.
Observed behavior
- A restored tab enters a repeated flash/reload state instead of settling into a live terminal.
- Failed restored terminals show the exact viewport/scrollback error:
execvp(3) failed.: No such file or directory
- Live server logs show repeated
terminal.create requests for the same opencode session within a very short interval.
Evidence
- The server is spawning
opencode via the default command path in server/terminal-registry.ts (defaultCommand: 'opencode' and the spawn path in create / buildSpawnSpec).
- On one websocket connection, the logs show about 25
terminal.create requests for the same session ses_2ce430133ffeKBVU9XrVPUTgPx within roughly half a second.
- Those requests all attempt to spawn:
opencode --model google/gemini-3-pro-preview --session ses_2ce430133ffeKBVU9XrVPUTgPx
TerminalView currently treats INVALID_TERMINAL_ID as reconnectable and generates a new createRequestId, which causes the repeated respawn loop.
- These reconnect creates are restore-marked and bypass ordinary rate limiting, which is correct for a real reconnect after loss, but dangerous when the terminal never successfully starts.
server/terminal-registry.ts clears binding on exit, so exited opencode terminals no longer expose resumeSessionId in inventory.
Root cause chain
- A restored
opencode terminal fails to start because the executable cannot be spawned.
- The terminal immediately exits with
execvp(3) failed.: No such file or directory.
- The client sees
INVALID_TERMINAL_ID from attach/restore flow and treats it as reconnectable.
TerminalView generates a new create request.
- The server honors the restore-marked create and bypasses normal rate limiting.
- The loop repeats rapidly, producing flashing tabs and repeated spawn attempts.
Why this is not just a rate-limiting problem
Restore is supposed to allow many rapid reconnects when the server genuinely restarts or the session needs fast rebinding. A fix that simply slows restore retries would break the intended UX and still leave the infinite loop path open.
The fix needs to distinguish:
- legitimate burst reconnect recovery, where dozens of reconnect attempts may be correct and necessary, from
- an infinite retry loop caused by a hard startup failure or immediately exited terminal.
Candidate directions for the fix
- Stop retrying when startup fails hard or the terminal exits immediately.
- Treat
execvp / missing-command startup failure as terminal-fatal.
- Gate reconnect on proof that the prior terminal was actually live and later lost, rather than failed to start.
Acceptance criteria
- Failed
opencode startup does not trigger repeated restore respawns.
- Legitimate restore scenarios still allow many rapid reconnects.
- A terminal that exits immediately with a startup failure is surfaced as failed, not endlessly retried.
- The UI stops flashing/reloading in this failure mode and shows a stable failure state instead.
References
src/components/TerminalView.tsx around the terminal.exit handler and INVALID_TERMINAL_ID reconnect path.
server/ws-handler.ts around terminal.create, restore bypass of rate limiting, and terminal.attach returning INVALID_TERMINAL_ID.
server/terminal-registry.ts around the opencode default command and PTY spawn.
Additional context
- Repo remote:
https://github.com/danshapiro/freshell.git
- The server tab inventory showed an
OpenCode tab and many exited opencode terminals.
/api/terminals/<id>/viewport for failed opencode terminals returned the startup error above.
~/.freshell/logs/server-debug.production.3001.jsonl contains the repeated spawn lines for the same session.
Problem
Restored
OpenCodetabs can flash/reload repeatedly and never become usable. This appears to be an infinite restore retry loop around failedopencodeterminals, not a normal reconnect burst.Observed behavior
execvp(3) failed.: No such file or directoryterminal.createrequests for the sameopencodesession within a very short interval.Evidence
opencodevia the default command path inserver/terminal-registry.ts(defaultCommand: 'opencode'and the spawn path increate/buildSpawnSpec).terminal.createrequests for the same sessionses_2ce430133ffeKBVU9XrVPUTgPxwithin roughly half a second.opencode --model google/gemini-3-pro-preview --session ses_2ce430133ffeKBVU9XrVPUTgPxTerminalViewcurrently treatsINVALID_TERMINAL_IDas reconnectable and generates a newcreateRequestId, which causes the repeated respawn loop.server/terminal-registry.tsclears binding on exit, so exitedopencodeterminals no longer exposeresumeSessionIdin inventory.Root cause chain
opencodeterminal fails to start because the executable cannot be spawned.execvp(3) failed.: No such file or directory.INVALID_TERMINAL_IDfrom attach/restore flow and treats it as reconnectable.TerminalViewgenerates a new create request.Why this is not just a rate-limiting problem
Restore is supposed to allow many rapid reconnects when the server genuinely restarts or the session needs fast rebinding. A fix that simply slows restore retries would break the intended UX and still leave the infinite loop path open.
The fix needs to distinguish:
Candidate directions for the fix
execvp/ missing-command startup failure as terminal-fatal.Acceptance criteria
opencodestartup does not trigger repeated restore respawns.References
src/components/TerminalView.tsxaround theterminal.exithandler andINVALID_TERMINAL_IDreconnect path.server/ws-handler.tsaroundterminal.create, restore bypass of rate limiting, andterminal.attachreturningINVALID_TERMINAL_ID.server/terminal-registry.tsaround theopencodedefault command and PTY spawn.Additional context
https://github.com/danshapiro/freshell.gitOpenCodetab and many exitedopencodeterminals./api/terminals/<id>/viewportfor failedopencodeterminals returned the startup error above.~/.freshell/logs/server-debug.production.3001.jsonlcontains the repeated spawn lines for the same session.