Skip to content

[bug] Chat cannot open a coding session — only the Loop can, so "what's the terminal doing?" fails while "go fix it" silently starts one #407

Description

@serge-ivo

Reported

"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:

const session = await getActiveSessionForRepo(env, ctx.agentId, ctx.userId, repo.id);
if (!session) return fail(call.name, `No active session for "${repoName}".`);

So the same agent, in the same conversation, about the same repo:

  • "go fix issue Document platform architecture #12"start_work → loop driver → ensureActiveSessiona session opens;
  • "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

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_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.

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    backendBackend / Worker / API workbugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions