Skip to content

[Bug]: A headless claude -p started by another agent inherits the pane's Orca identity and fires notifications for that pane #26020

Open

Description

@whisperice

Operating system

macOS 26.6.2 (25G83), Apple Silicon (arm64)

Orca version

1.4.221 (build e65bbc95cec14b365e5cb22e3fdfdff413c1f82b). Also seen on 1.4.218.

Agents

The pane's agent is OMP (oh-my-pi) 18.6.1. Claude Code 2.1.284 runs as its child.

Details

Short summary

Our coding agent (OMP) runs tools that start headless claude -p processes, such as review jobs and one-off probes. These child processes inherit the pane's ORCA_* environment. Orca's managed Claude hook therefore posts their lifecycle events as if they came from the pane's own agent.

As a result, Orca sends Agent Task Complete notifications for the parent pane while the parent agent is still working. The pane's status row also switches to the child Claude session.

What happened

Case 1, Orca 1.4.218. A local code-review harness started one claude -p reviewer per lens from an OMP pane. Each time a reviewer finished, Orca sent a completion notification for the OMP pane. OMP was still waiting for the whole review at that point.

Case 2, Orca 1.4.221. An OMP pane ran a subagent. That subagent used its bash tool to run three short headless Claude commands from /tmp, started between 17:57:02 and 17:57:26 local time:

claude -p "Reply with the single word ok" --model <model>

OMP was still working throughout.

  • Orca minted three agent notifications for the OMP pane, one per child run. Their IDs in mobile-notification-dismissals.json carry turn start times of 17:57:05, 17:57:09 and 17:57:25.
  • Afterwards, the pane's persisted row in agent-hooks/last-status.json was a Claude row, not an OMP row:
    { "source": "claude", "hookEventName": "StopFailure",
      "payload": { "state": "done", "prompt": "Reply with the single word ok", "agentType": "claude" },
      "providerSession": { "transcriptPath": "~/.claude/projects/-private-tmp/<id>.jsonl" },
      "providerSessionOnly": true }

How to reproduce

  1. Start a non-Claude agent in an Orca terminal. I used OMP.
  2. From that agent's shell tool, run claude -p "Reply with the single word ok".
  3. An Agent Task Complete notification arrives for the pane, and the pane's status row becomes the Claude child's row.

Expected

A child agent CLI started by the pane's agent should not change the pane's identity, settle the pane's turn, or fire Agent Task Complete. If you think it should notify, please make that a setting that is off by default.

What I found in the code (at e65bbc9)

  • The managed hook acts on whatever ORCA_PANE_KEY it inherits. It exits early only when there is no payload, when there is no pane key, or through a per-caller guard for Grok (grok-replay-guard.ts).
  • The hook already captures the agent's PID and start time (ORCA_HOOK_AGENT_PID="${PPID:-}" in hook-process-capture.ts).
  • shared/agent-status-identity.ts (resolveAgentStatusIdentity / shouldSuppressInheritedTerminalStatus) already tries to stop a nested child from taking over a pane. It suppresses the child's done while the parent row is fresh and not done.
  • In case 2, the pane still ended up with the child's Claude row and notifications. I have not traced why the fence did not apply. One possibility is that the child creates its own provider-session row.

Related: #13341 (a nested agent replaces the parent's icon), #25686 (a nested codex exec model leaks into the parent row), #20109 (a nested Grok fires Agent Task Complete), #10999 (a nested claude -p is captured as an automation reply).

Suggested fix

  1. Detect nesting from the process lineage. If the agent PID that the hook reports descends from a different recognised agent running in the same pane, treat its events as child work. Keep the pane's identity, do not settle the turn, and do not notify. selectForegroundProcessCandidate already applies this "outer agent wins" rule to foreground identity.
  2. Add a documented opt-out that every managed hook checks first, for example ORCA_AGENT_HOOKS=off. Tools could then mark the child agents they start. Today the only option is to strip every ORCA_* variable, which depends on Orca's internal variable names.
  3. Optionally, add a setting for whether nested agent runs may notify, because teams differ on this.

Workaround we use

Our harness removes every ORCA_* variable from the environment of the claude -p children it starts. I verified this against a fake local hook receiver:

  • An unsanitised child produced 1 POST.
  • A sanitised child produced 0 POSTs and no spool entry.

This only covers launchers that go through our helper. Ad-hoc claude -p runs, like case 2, are still affected.

Activity

  1. AmethystLiang commented on Oct 7, 2026

    @AmethystLiang
    Contributor

    Thanks for reporting. Looking into it

  2. self-assigned this
    on Oct 7, 2026
  3. tianlrz commented on Oct 16, 2026

    @tianlrz

    Additional real-device report and local evidence from Orca 1.4.218 on macOS with a paired iOS client, 2026-10-16. This extends the inherited-pane problem to headless codex exec children of a Claude pane. Related: #25686 and the owner-isolation work in #26408.

    User-visible symptom

    The phone repeatedly shows notifications for the same workspace containing raw child results such as:

    {"checked":86,"errors":[]}

    Here checked is the number of numeric claims examined by a batch reviewer, and errors is that reviewer's finding list. It is application output from a child job, not an Orca diagnostic and not the parent agent's final reply.

    What was verified locally

    • The affected workspace contains a long-lived interactive Claude pane running batch review jobs.
    • Its review script starts one CLI per item using Python subprocess.run, with captured stdout/stderr and no explicit replacement environment. The Codex path uses codex exec --skip-git-repo-check -s read-only ... -o <output> -; the Claude path uses claude -p ... --no-session-persistence. These children inherit the launcher's environment.
    • A persisted Codex exec transcript contains the exact assistant response {"checked":86,"errors":[]} at 19:39:16.055 +08:00.
    • mobile-notification-dismissals.json contains a distinct notification ID for the parent Claude pane whose encoded state-start timestamp is 19:39:16.150, just 95 ms later.
    • In a bounded scan of recent small exec transcripts, 23 child final-response timestamps matched notification IDs for that same parent pane within 1.2 seconds. Examples:
    Child result Child final response (+08:00) Parent-pane ID timestamp (+08:00) Difference
    checked: 86, errors: [] 19:39:16.055 19:39:16.150 95 ms
    checked: 99, errors: [] 19:40:19.079 19:40:19.163 84 ms
    checked: 73, errors: [] 19:41:50.912 19:41:51.009 97 ms

    The parent's own transcript continued afterward and produced its actual summary at about 19:42:36. The individual reviewer results preceded that summary.

    The saved notification registry contains 64 distinct records for this pane in the hour ending at 19:42:36, with a 17.87-second median interval between their encoded timestamps. Those are host-side notification records, not a measurement of 64 APNs deliveries. The repeated iOS banners are the user's observation; device-side push arrival logs were not collected.

    Expected behavior / regression coverage

    A batch child may return its machine-readable result to the parent, but must not settle the parent pane, replace its displayed response, or trigger a completion push for each item. Keep the genuine parent completion and input-needed notifications.

    Please include both Claude -> codex exec and same-provider Claude -> claude -p batch runs in owner-isolation regression coverage, and assert the downstream notification count as well as the status row. The same-provider case matters because an agent-type-only fence cannot distinguish two Claude sessions. I have not tested #26408 against this workload and am not claiming it already resolves the report.

    Inspection was read-only: no running task, hook configuration, notification preference, or child environment was changed. Raw transcripts, prompts and pane identifiers are omitted here.

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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions