Skip to content

Custom (non-full-lifecycle) hook-reported agent state is cleared by local detection, dropping the pane from the sidebar #4020

Description

@beantownbytes

Summary

When agent lifecycle state is reported through a custom hook source (a source
not on herdr's full_lifecycle_hook_authority allow-list), herdr's own screen/process
detection can clear that pushed authority as soon as detection stops recognizing
the agent on screen. Because detection is never paused for a non-full-lifecycle source,
the pane then has neither a hook authority nor a detected agent, so
effective_agent_label() returns None, is_agent_terminal() is false, and the pane
drops out of the agent sidebar entirely (blank, no state) even while the agent is
actively working.

This is especially visible when the agent runs somewhere herdr cannot introspect
locally (e.g. inside a container/sandbox), so herdr's own detection cannot corroborate
the pushed state, yet the push is the only authoritative signal.

Environment

  • herdr 0.8.2 (line references below are from a recent main checkout, commit
    9ad65d9).
  • macOS host; agent runs inside a Linux container and reports state over the control
    socket via pane.report_agent using a custom source (e.g. custom:sbh-pi,
    agent: "pi"). Reports are coarse transitions (working/idle/blocked/release) at turn
    boundaries.

Root cause

  1. full_lifecycle_hook_authority (src/detect/mod.rs:316) only matches a fixed set of
    sources (herdr:pi, herdr:omp, herdr:opencode, herdr:kilo, herdr:kimi,
    herdr:mastracode). A custom source is not on it.
  2. Because the source is not full-lifecycle, herdr never pauses its own detection for
    the pane (src/app/api.rs:438 sync_full_lifecycle_authority_detection_pauses).
  3. On a detection update where the previously-detected agent is lost, herdr clears the
    pushed hook authority (src/terminal/state.rs:553-576 -> self.hook_authority = None
    plus FullLifecycleHookSuppressionReason::HookClear).
  4. With the authority cleared and no agent detected, effective_agent_label()
    (src/terminal/state.rs:1812) is None, so is_agent_terminal()
    (src/terminal/state.rs:2094) is false and the pane is no longer listed as an agent.
  5. The reporter only pushes at transitions, so nothing re-establishes the state until
    the next transition; herdr also does not recover the agent when its screen marker
    returns (HookClear suppression), so the pane stays blank.

Reproduction

Two identical agents (same terminal title, same cwd), both actively working, both
showing the agent's live status text on screen, reporting state via the same custom
source over the control socket:

  • Pane A: herdr pane list -> {"agent":"pi","agent_status":"working"} (correct).
  • Pane B: herdr pane list -> no agent key, "agent_status":"unknown" -> blank
    in the sidebar, even though the pane is mid-work (live "working" status text visible
    on screen).

Delivery is not the problem: a test pane.report_agent for a nonexistent pane returns
{"error":{"code":"pane_not_found"}}, i.e. herdr accepts and processes reports from the
custom source; the pane simply loses its agent afterward via the detection-clear path.

Expected vs actual

  • Expected: a pane whose agent state is pushed by a hook keeps showing that state until
    the hook says otherwise (or releases), regardless of whether herdr's own screen/process
    detection currently recognizes the agent.
  • Actual: herdr's own detection clears the pushed authority for a non-full-lifecycle
    source, and the pane disappears from the agent list.

Suggested fix

Introduce a source category for externally/remotely reported agents that (a) pauses
herdr's own screen/process detection for the pane like full-lifecycle sources do, and
(b) stays effective without requiring local detected_agent to match -- because the
agent may run where herdr cannot introspect it (a container/sandbox).

Note a subtlety: simply adding such sources to the existing full_lifecycle_hook_authority
list is not sufficient, because hook_authority_is_effective
(src/terminal/state.rs:1805) then requires detected_agent == Some(agent) (its second
branch), which fails when herdr cannot detect the agent locally and would nullify the
pushed state. A "remote/proxy full-lifecycle" category needs to satisfy both (a) and (b).

Happy to test a patch against this setup.

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 classificationbugSomething isn't workingintegrationsmanaged hooks and integration installation or statusmacosaffects macOS-specific behaviormaintainer-neededrequires maintainer judgment or maintainer-only reproductionp2valid narrow or ordinary defect with limited impact or a practical workaroundremoteaffects SSH, headless, or remote attachment behavior

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions