Skip to content

Response viewer stays pinned to the launch conversation for a pane driven from tmux #366

Description

@shenlvkang-collab

Response viewer stays pinned to the launch conversation for a pane driven from tmux

Version 1.24.1 · Install git clone · Mode claude (CLI 2.1.251) · OS Linux (WSL2) · Surface Response Viewer / GET /api/sessions/:id/last-response

What you see

I attach to a pane's tmux session directly (tmux -L codeman attach -t codeman-<id8>) rather
than typing into Codeman's web terminal. After a /clear, the response viewer keeps showing
the pre-/clear transcript, permanently — not one turn behind, but forever.

Why

resolveActiveClaudeSessionIdFromHistory() (src/web/routes/session-routes.ts) is the only
thing that can notice a /clear, and it opens with:

const submitAt = session.lastSubmitAt;
if (!submitAt) return null; // never typed through Codeman — nothing to credit

_lastSubmitAt is bumped only by _trackSubmit(), reachable only from Session.write() and
Session.writeViaMux() — i.e. input that flows through Codeman. A pane driven straight
from tmux never sets it, so on my machine ~/.codeman/state.json carries no lastSubmitAt
for either live Claude pane and 100% of calls return at that line.

The docs already anticipate the correlation being unavailable and choose the safe behaviour
("a viewer one turn behind beats a viewer showing someone else's conversation"), so this is
not a wrong-conversation bug — it is a never-updates bug for a whole class of user.

Why the obvious fix is wrong

Relaxing the guard — dropping it, or synthesising an anchor from PTY idle→busy — recreates
exactly the bug session-routes.ts documents as already made and reverted: keying off
"newest history entry for this cwd" made the eye follow a stranger's conversation. Measured
on one folder here, three live panes share a workingDir and the minimum gap between
adjacent prompts of two different conversations is 2.3 s, well inside
CLAUDE_SUBMIT_MATCH_MS = 10_000.

Proposed fix

Add a UserPromptSubmit hook. Its payload carries session_id — the pane's live
conversation, reported from inside the CLI process — and it reaches Codeman addressed by that
pane's own $CODEMAN_SESSION_ID. That binding is a fact, not a correlation: it never
consults workingDir, so no sibling pane can claim it. A pane holding such an id skips the
correlation entirely, so the number of prompts eligible for cwd-based guessing goes down,
never up. The hook also stamps lastSubmitAt, so it finally means "a prompt was submitted".

One implementation note worth stating up front, because it is easy to get wrong: the hook's
stdout must be discarded with curl's own -o /dev/null. Claude Code injects a
UserPromptSubmit hook's stdout into the model's context ("Exit code 0 - stdout shown to
Claude" in the CLI's own hook reference), and a trailing >/dev/null does not work — the
existing curlCmd ends … 2>/dev/null || true, and in pipeline || true >/dev/null the
shell binds the redirection to true, which never runs on the success path.

Branch is ready with tests and the full gate green — happy to open the PR.

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

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions