Skip to content

Claude Code pane in a turn: agent explain reports working, agent list / pane get report idle #3993

Description

@AWenSu

Is this a reproducible bug?

  • I confirm this is a reproducible bug, not a feature request, idea, question, contribution proposal, or direction check.
  • I reproduced this bug on the version and environment reported below using the exact steps provided.

Current behavior

For a Claude Code pane that is in the middle of a turn, herdr agent explain and
herdr agent list / pane get disagree. The detection engine classifies the pane as
working, but the reported agent_status stays idle.

Same pane, same second:

$ herdr agent explain wF:p9 --verbose
agent: claude
state: working
manifest: remote:.../claude.toml 2026.09.11.1
rule: live_turn_working (region=bottom_non_empty_lines(12) priority=970)
visible: idle=false blocker=false working=true

$ herdr pane get wF:p9
agent: claude
agent_status: idle
agent_session.source: herdr:claude

The divergence is stable, not a sampling race. I sampled both every 2s for 10s while the
pane kept producing output: explain returned working on every sample, list returned
idle on every sample.

Two panes were genuinely in a turn at the time and both showed the split. The other 14
idle panes agreed between the two commands, so only the working transition is affected.

Expected behavior

agent list / pane get should report the state the detection engine computes, so the
sidebar, agent wait, and notifications match agent explain.

Reproduction

  1. Start Claude Code in a Herdr pane and give it a task that runs for a while.
  2. While the turn is running, run herdr agent explain <pane> --verbose — it reports
    state: working, matched rule live_turn_working, visible: working=true.
  3. In the same second, run herdr pane get <pane> — agent_status is idle.
  4. Repeat step 2 and 3 every 2 seconds. The split persists for the whole turn.

What I ruled out

  • Not a stale custom lifecycle source. I had a custom pane report-agent source on
    these panes. I released it from all 16 panes (pane release-agent --source ... --agent claude)
    and the split was unchanged. agent_session.source is herdr:claude.
  • Not a stale manifest. server reload-agent-manifests and server update-agent-manifests
    both ran; claude manifest is 2026.09.11.1, remote_update_result: current,
    no local override. Split unchanged after each.
  • Not Claude integration always report "idle" state despite working #1630. CLAUDE_CODE_DISABLE_TERMINAL_TITLE is 0 here, terminal titles are enabled.
  • Not the done/seen semantics from What marks a pane seen, and do external reports take effect? #3912. The panes report idle, not done.

Possibly related: osc_title_working never matches on this setup

explain --verbose shows osc_title_working (priority 1100) evaluating to ✗ on every pane:

✗ osc_title_working priority=1100 region=osc_title state=working
  regex: ^[\x{2800}-\x{28FF}\x{25D0}-\x{25D3}]
  region preview: "✳ review claude"

Claude Code 2.1.269 sets the terminal title with a leading ✳ (U+2733 EIGHT SPOKED
ASTERISK). The regex only accepts U+2800–U+28FF (braille) and U+25D0–U+25D3 (half circles),
so U+2733 falls outside it. All 16 Claude panes on this machine have a title starting with
U+2733.

I am not claiming U+2733 by itself means "busy" — idle panes carry it too, since the field
holds the last title seen. But if the stored agent_status is driven by the OSC title path
while explain re-evaluates the full rule set on demand, a title rule that can never match
would explain why the stored state never leaves idle even though a lower-priority screen
rule matches. That is a hypothesis, not something I verified from the inside.

This looks like the same class as #2760, which extended the same rule for half-circle
spinner frames.

Impact

agent wait --until idle returns immediately for a pane that is still working, so
CLI-driven orchestration treats a dispatched task as finished the moment it is dispatched.
The sidebar shows the same wrong state, so there is no visual signal either. I worked around
it by having Claude Code's own UserPromptSubmit / Stop hooks write turn state to a file
and reading that instead, but that only helps callers who install that hook.

Related: #1630 (closed, different cause), #3414 (inverse direction), #3912 (external
reports), #2760 (same rule, spinner glyph coverage).

Environment

  • Herdr version: 0.9.0 (Homebrew), client and server both 0.9.0, protocol 22
  • Update channel (stable or preview): stable
  • Operating system: macOS 26.6.2 (arm64)
  • Terminal: Ghostty
  • Shell, if relevant: zsh
  • Relevant config, if any: Claude Code 2.1.269; Claude detection manifest 2026.09.11.1
    (remote, no local override); Claude integration hook v9; CLAUDE_CODE_DISABLE_TERMINAL_TITLE=0

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

    agent-detectionagent recognition or working, blocked, idle, and lifecycle classificationapipublic JSON API, events, commands, or wire protocolmacosaffects macOS-specific behaviormaintainer-neededrequires maintainer judgment or maintainer-only reproductionp2valid narrow or ordinary defect with limited impact or a practical workaround

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions