[Bug]: Grok Agent Task Complete fires mid-run, and when Claude/Codex nested-launches Grok
- Dominant language
- TypeScript
- Stars
- 71.3k
- Forks
- 4.7k
- Avg merge
- 17h 8m
- Merged PRs (30d)
- 475
Description
### Operating system
macOS
### Orca version
v1.4.198; Grok Build 1.0.25 (f7e67d6988e2) [stable]
### Details
Short summary:
With Settings → Notifications → Agent Task Complete enabled, Orca dings for Grok before the pane is actually idle. Two cases:
1. A native Grok pane can chime mid-run (tool/task boundaries), not only when Grok is waiting for the next prompt. Claude and Codex already chime once, at real idle.
2. When Claude (or another lead agent) launches Grok as a nested CLI in the same pane, Grok inherits `ORCA_PANE_KEY`. Grok `Stop` / `SessionEnd` then settle **that Claude pane** as `done` and fire Agent Task Complete while Claude is still waiting on the child.
What happened?
**Native Grok pane.** Agent Task Complete is supposed to mean "coding agent finishes and becomes idle." Grok still reports working/done through Orca's managed hooks and OSC titles. Two ways a mid-run ding leaks through:
- `normalizeGrokEvent` maps `Stop` / `SessionEnd` / `StopFailure` to pane `done` with no child roster. `Notification` is installed with no matcher. Idle is inferred from **message text** (`type your message`, `enter send`, …), not from `notificationType`. Grok documents `idle_prompt` / `task_complete` / `permission_prompt` and says hosts should match `notificationType`, not display copy. A `task_complete` (or other Notification) whose message looks like the TUI footer becomes pane `done`.
- Grok working titles are `spinner - - grok`. Orca already special-cases the rotating frame (`isGrokRotatingWorkingTitle`). A frame without the spinner (`… - grok`) classifies as idle. Between tools the spinner can drop, which is a title-path Agent Task Complete.
Claude keeps a working marker for the whole turn and keeps the parent `working` while the subagent roster is live. Grok's mapper does neither.
**Cross-model nested Grok (this is not #19557).** #19557 is Grok `spawn_subagent` inside a Grok pane (`subagentType` on the child payload). This report is Claude/Codex as the **lead**, Grok as a **nested process**.
Grok's managed hook (`~/.orca/agent-hooks/grok-hook.sh`, template `kr()` in the grok installer) has no nested-host skip. Claude's own hook already bails on `CLAUDE_JOB_DIR`. Nested Grok inherits `CLAUDECODE=1` and the parent pane key, then POSTs `/hook/grok` onto the Claude pane.
Live check on this machine (Orca 1.4.198, two terminals, same worktree, same Grok prompt: read a file, `list_dir`, stop):
| Run | Hook decisions posted to Orca |
| --- | --- |
| Native Grok pane | 9 posts, including `Stop` ×2 (real end of run) |
| Claude `-p` Bash-launching that same Grok | **0 posts**; 9 events **skipped** once `CLAUDECODE=1` was set, including `Stop` ×2 and `SessionEnd` |
Without a skip, those nested `Stop`s are pane `done` on the Claude tab. Agent Task Complete fires while Claude is still on the turn. Focus-suppression hides this if you are staring at a native Grok pane; it does not hide it if you are watching Claude and Grok is the child.
How can we reproduce it?
Native extra chimes:
1. macOS, Orca 1.4.198, Agent Task Complete on. Open a Grok pane. Unfocus Orca or the worktree (or turn Suppress when focused off).
2. Give Grok a turn that uses several tools.
3. Hear Agent Task Complete before the TUI is waiting for the next prompt. Claude/Codex on the same setting only ding at real idle.
Cross-model nested Grok:
1. Same notification setting. Open Claude in an Orca pane.
2. Ask Claude to run Grok via Bash, e.g. `grok --always-approve --no-subagents -p 'Read a file, list a directory, stop.'`
3. While Grok is running (or when it `Stop`s), Orca treats the **Claude** pane as task-complete. Claude is still the lead and has not gone idle.
Expected:
- Agent Task Complete only when the **lead** session on that pane is idle and waiting for the user.
- Nested Grok under Claude/Codex must not settle the parent pane. Same rule as Claude's roster and as #19722 states for same-CLI descendants: a descendant lifecycle event must not fire completion.
- Native Grok should not complete on per-tool Notification / spinner-less titles.
Not expected:
- One ding per Grok tool or nested child.
- Grok `Stop` on a Claude pane key.
- Local edits to `grok-hook.sh` as the fix. Orca's `refreshManagedScripts` / `install()` rewrite that file from the stock template.
Anything else that might help:
- Related: #19557 (Grok-in-Grok `spawn_subagent` Stop → Agent Task Complete). Same user-visible ding, different parent. #19722 (`subagentType` / descendant roster) would not catch this Claude-parent case: the nested Grok process is a full session (`SessionStart` … `Stop`) with `CLAUDECODE=1` and **no** Grok `subagentType`.
- Related, opposite sidebar symptom: #15225 / #15230 (`idle_prompt` / `task_complete` → `done`). Mapping `task_complete` to lead `done` can make native extra chimes worse if Grok emits that type mid-turn. Grok docs: match `notificationType == idle_prompt` for host idle, not every Notification.
- Source on 1.4.198: `src/main/grok/grok-hook-script.ts` (stock `grok-hook.sh`; no `CLAUDECODE` / `CLAUDE_JOB_DIR` skip), `src/shared/agent-hook-listener/providers/grok-events.ts` (`normalizeGrokEvent`), title idle via `isGrokRotatingWorkingTitle` / `detectAgentStatusFromTitle`.
- Suggested product fix:
1. In the managed Grok hook template, skip (after draining stdin) when `CLAUDECODE`, `CLAUDE_CODE`, or `CLAUDE_JOB_DIR` is set. Same idea as Claude's `CLAUDE_JOB_DIR` bail. That is the only skip that survives `refreshManagedScripts`.
2. Treat Grok lead idle as `notificationType == idle_prompt` (and lead `Stop` only when it is not a nested/foreign-host session). Do not complete on `task_complete` or footer message heuristics.
3. Do not classify spinner-less `… - grok` titles as idle while hooks still say working.
Contributor guide
Research direction
Start with src/main/grok/grok-hook-script.ts, src/shared/agent-hook-listener/providers/grok-events.ts, and the title handling around isGrokRotatingWorkingTitle and detectAgentStatusFromTitle. Reproduce both the native multi-tool run and the Claude or Codex nested Grok run, then verify that only the lead session's true idle state triggers completion and that nested or intermediate Grok events do not.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- shell, typescript
- Domain
- cli, devtools
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 55/100