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

Closed

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 → ensureActiveSession → a 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

  1. added
    bugSomething isn't working
    backendBackend / Worker / API work
    on Aug 8, 2026
  2. serge-ivo commented on Aug 8, 2026

    @serge-ivo
    ContributorAuthor

    Shipped in 5768194, deployed to the API worker (run 31234351408, success; /health green).

    What changed. read_terminal and send_to_cli in lib/storage-tools.ts no longer give up on getActiveSessionForRepo returning null — they go through a new ensureSessionForChat in lib/coding-session-open.ts, which wraps the same ensureActiveSession the Loop uses behind a connectivity gate. The gate is the load-bearing part: ensureActiveSession calls createSession before it discovers there is no runner and then ends the row, which is fine for a Loop that 409s either way and not fine for a read tool called repeatedly. Runner offline now returns the diagnosis and writes nothing.

    Acceptance, each with a test:

    Verified: pnpm typecheck (10/10), pnpm vitest run (373 files, 5969 tests, +13), biome, design tokens, migration hygiene — all re-run after rebasing onto main, then pushed.

    Not verified live. The end-to-end check needs a machine running pags up, which I do not start. On a Repo Coder with the runner up and no session, ask "what's the terminal doing?" and confirm the reply names a newly started session; then stop pags up and confirm the reply blames the runner and coding_sessions gains no row.

    Left undone, deliberately: list_coding_repos does not mention a reaped session. It needs a per-repo query, and the reap is already reported at the moment it matters — the next read_terminal. Say if you want that filed.

    Note: CI on main is red for an unrelated reason (the #384 WebKit Preferences guard, red for 12 commits before this one); the API deploy workflow is independent and passed.

  3. serge-ivo commented on Aug 8, 2026

    @serge-ivo
    ContributorAuthor

    Verified already delivered — closing.

    5768194 is on origin/main and deployed. Re-checked against the code rather than the commit message:

    • lib/storage-tools.ts:645,703 — both read_terminal and send_to_cli now go through ensureSessionForChat, not the bare getActiveSessionForRepo null-check this issue was filed against.
    • lib/coding-session-open.ts:226 — ensureSessionForChat checks runtimeConnectivity before ensureActiveSession, so the runner-offline path returns noSessionMessage(...) and creates no coding_sessions row. That ordering is the load-bearing part and it is the ordering in the file.
    • sessionOpenedNotice names repo, node, engine and session id, and carries the reap sentence when lastIdleReapForRepo finds one.
    • IDLE_SESSION_MS is untouched at 6h.

    All four acceptance boxes hold in code. Still not verified live — that needs a machine running pags up, and both of the owner's machines are on stale CLIs (Mac 0.4.40, Mac-mini 0.4.22). The live check is unchanged from the shipping comment: with the runner up and no session, ask a Repo Coder "what's the terminal doing?" and confirm the reply names a session it started; stop pags up and confirm the reply blames the runner and coding_sessions gains no row.

    The one deliberate omission stands: list_coding_repos does not mention a reaped session. File it separately if wanted.

    #408 is where the rest of this lives, and it is open.

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