You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
"Why do I need to always start the session manually? If it expires, can't the agent just start it
when I have a conversation with it?"
Because the chat path is the only path that cannot. This is not a policy decision — it is a missing
call.
The asymmetry, in two lines of code
lib/coding-session-open.ts:102 exports ensureActiveSession(env, instanceId, userId, repo). It
reuses a live session, re-attaches one whose engine process has gone (runner restarted, laptop
slept), opens a fresh one when there is none, and reports why when it cannot. It is idempotent and
it is already the thing that makes the Loop reliable.
It has exactly one caller: lib/loop-drivers.ts:181 — the Loop / Pilot path.
The chat tools do not use it. lib/storage-tools.ts:637 and :682:
constsession=awaitgetActiveSessionForRepo(env,ctx.agentId,ctx.userId,repo.id);if(!session)returnfail(call.name,`No active session for "${repoName}".`);
So the same agent, in the same conversation, about the same repo:
"what's the terminal showing?" → read_terminal → "No active session", and you are told to
go and press a button.
One of those spawns a process on your laptop. The other refuses to look at one. The path with the
larger consequence is the one that self-serves.
What a session actually is (for the ticket's readers)
A coding_sessions row plus a real child process on the owner's machine — the claude CLI
itself, spawned by the runner (HeadlessSession). Not tmux: tmux sessions survive forever because a
tmux server owns them and nothing reaps them. Here the platform owns a live OS process, and coding-session-sweeper.ts:63 reaps it after IDLE_SESSION_MS = 6 hours with nothing touching
it — no capture poll, no message, no run, no Co-pilot turn, no timeline read. The header states the
reason plainly: otherwise a claude --dangerously-skip-permissions child stays resident
indefinitely, one per repo, accumulating context forever.
That policy is right and this ticket does not propose changing it. Six idle hours is not the
problem; needing a button afterwards is.
Do
Chat tools that need a session should ensure one, via the same ensureActiveSession the
Loop uses — read_terminal, send_to_cli, and anything else that currently fails on getActiveSessionForRepo returning null.
Say so in the reply. "Started a session for apps/chess-academy" — a process appearing on
someone's laptop should never be silent, and the agent is mid-conversation with them anyway.
Keep it manual, because spawning a process on someone's machine deserves an explicit act. That
argument is real, and it is already lost: start_work from the same chat opens one without asking,
and so does the Loop button. The current split does not protect anyone — it only makes the harmless
half (reading a pane) harder than the consequential half (driving the engine). If an explicit act is
genuinely wanted, it belongs on the WRITE path, not on read_terminal.
Acceptance
Asking a Repo Coder about its terminal with no live session opens one and answers, in one turn.
The reply names the session it started.
With the runner offline, the reply is the offline diagnosis, and no session row is created.
The 6-hour idle reap is unchanged, and a reaped session is visible from the chat surface.
Reported
Because the chat path is the only path that cannot. This is not a policy decision — it is a missing
call.
The asymmetry, in two lines of code
lib/coding-session-open.ts:102exportsensureActiveSession(env, instanceId, userId, repo). Itreuses a live session, re-attaches one whose engine process has gone (runner restarted, laptop
slept), opens a fresh one when there is none, and reports why when it cannot. It is idempotent and
it is already the thing that makes the Loop reliable.
It has exactly one caller:
lib/loop-drivers.ts:181— the Loop / Pilot path.The chat tools do not use it.
lib/storage-tools.ts:637and:682:So the same agent, in the same conversation, about the same repo:
start_work→ loop driver →ensureActiveSession→ a session opens;read_terminal→ "No active session", and you are told togo and press a button.
One of those spawns a process on your laptop. The other refuses to look at one. The path with the
larger consequence is the one that self-serves.
What a session actually is (for the ticket's readers)
A
coding_sessionsrow plus a real child process on the owner's machine — theclaudeCLIitself, spawned by the runner (
HeadlessSession). Not tmux: tmux sessions survive forever because atmux server owns them and nothing reaps them. Here the platform owns a live OS process, and
coding-session-sweeper.ts:63reaps it afterIDLE_SESSION_MS= 6 hours with nothing touchingit — no capture poll, no message, no run, no Co-pilot turn, no timeline read. The header states the
reason plainly: otherwise a
claude --dangerously-skip-permissionschild stays residentindefinitely, one per repo, accumulating context forever.
That policy is right and this ticket does not propose changing it. Six idle hours is not the
problem; needing a button afterwards is.
Do
ensureActiveSessiontheLoop uses —
read_terminal,send_to_cli, and anything else that currently fails ongetActiveSessionForReporeturning null.apps/chess-academy" — a process appearing onsomeone's laptop should never be silent, and the agent is mid-conversation with them anyway.
runner-offline one ([bug] The Terminal tab learns the runner is gone only by failing — and blanks that error every 4s, so a phone shows an empty pane and a flicker #378's treatment), not a session attempt that cannot work.
is visible in the Co-pilot view and nowhere else — so from chat, a session simply stops
existing and the next question fails for a reason nothing stated. Same shape as [bug] Stop on a loop looks like it does nothing — the server records the cancel and no UI ever reads cancelRequested #376/[bug] Hands-free gives up after four failed mic restarts and says nothing — no notice, no error-log row, indistinguishable from a crash #387: the
platform knows and does not say.
Alternative considered and rejected
Keep it manual, because spawning a process on someone's machine deserves an explicit act. That
argument is real, and it is already lost:
start_workfrom the same chat opens one without asking,and so does the Loop button. The current split does not protect anyone — it only makes the harmless
half (reading a pane) harder than the consequential half (driving the engine). If an explicit act is
genuinely wanted, it belongs on the WRITE path, not on
read_terminal.Acceptance
Files:
workers/api/src/lib/storage-tools.ts:637,682,workers/api/src/lib/coding-session-open.ts:102,workers/api/src/lib/loop-drivers.ts:181,workers/api/src/lib/coding-session-sweeper.ts:63.