Skip to content

fix(orchestration): stop the sender-terminal refusal recommending another pane's handle - #21097

Merged
brennanb2025 merged 2 commits into
mainfrom
brennanb2025/orchestration-refusal-guidance
Sep 18, 2026
Merged

brennanb2025 merged 2 commits into
mainfrom
brennanb2025/orchestration-refusal-guidance

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Sep 16, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 2 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​39 $\color{#cf222e}{\Huge{\mathbf{−}}}$​1 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​38
Prod 2 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​27 $\color{#cf222e}{\Huge{\mathbf{−}}}$​7 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​20

ELI5

When a native chat session tries to coordinate orchestration, Orca correctly refuses — then tells you to do the one thing that actually breaks something. This fixes the advice, not the refusal.

The bug

orchestration run-create from a structured chat session fails no_active_sender_terminal and says:

This chat session has no orchestration identity of its own, so --from cannot be inferred. Pass --from <terminal-handle> explicitly; guessing would act on another pane's mailbox.

So you run orca terminal list, pick a handle, and pass it. It works — resolveOrchestrationTerminalHandle returns on the explicit-flag branch before ever reaching the structured guard.

But a structured chat has no pane, so every handle you could have picked belongs to a different pane. The Run binds to that pane's mailbox, and because orchestration check is destructive by default, the next check consumes that worker's oldest unread batch and marks it read. The rightful worker never sees its mail.

The message warns about acting on another pane's mailbox while handing you the way to do it.

Where it came from

The guard and its wording were introduced together in #18827 (feat(orchestration): orchestrate native-born structured chat sessions, merged 2026-09-07). The guard itself is right and stays: a structured session genuinely cannot infer its identity, and refusing beats guessing onto a sibling. Only the remediation advice was wrong — it was written as though a handle existed that names the caller, and none does.

What changed

The structured refusal now states the real constraint — no handle names this session — and points at the path that works: create a worktree, create a terminal in it with an agent, then drive it with terminal send/read.

throwNoActiveSenderTerminal keeps suggesting --from, because its callers may genuinely have a handle, but now qualifies it as your own terminal's handle and says why another pane's is not a substitute.

No behavior change: same code, same refusal, same exit path. Message text only.

Testing

  • src/cli/handlers/orchestration-gate-cli.test.ts asserted on the old string. Updated — and while there, pinned ORCA_STRUCTURED_SESSION alongside the handle/pane-key vars the suite already controls.

    That test's outcome depended on ambient environment: run from a structured session it took the structured refusal, run from CI it took throwNoActiveSenderTerminal. It passed either way only because both messages happened to contain Pass --from <terminal-handle>. Removing that coincidence exposed it, so the env is now pinned rather than inherited.

  • 28 files / 275 tests pass across src/cli/handlers/orchestration, src/cli/orchestration, and the SSH orchestration suites.

  • Verified in both arms — with ORCA_STRUCTURED_SESSION set and with env -u ORCA_STRUCTURED_SESSION — 18/18 in the three directly affected suites either way.

  • pnpm tc:cli, pnpm tc:node, pnpm run check:code-quality:changed (0 findings across 2 files), formatted.

  • pnpm-lock.yaml absent from every commit and from the full branch range.

Visual proof

N/A — CLI error text; covered by the assertions above.

…ther pane's handle

The structured-session guard told callers to pass `--from <terminal-handle>`, but the
explicit-flag branch returns before that guard runs — so following the advice succeeds,
against a handle that necessarily belongs to a different pane, and the next `check`
consumes that pane's unread mail.

Both refusals now say what is actually true: no handle names a structured chat session,
and a caller that does have one should pass its own.

Also pins ORCA_STRUCTURED_SESSION in the gate CLI test, which until now decided which
refusal it exercised from ambient environment.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No critical issues — the copy change is correct and well-reasoned. One follow-up: the same refusal message survives unqualified on the SSH fallback path.

Reviewed changes

  • Structured-session refusal copy (terminal-identity.ts) — replaces the "pass --from <handle>" remediation with the real constraint (no handle names the session) plus a direct-drive workaround.
  • throwNoActiveSenderTerminal copy — qualifies --from as your own terminal's handle and warns that another pane's handle is not a substitute.
  • Test env pinning (orchestration-gate-cli.test.ts) — pins ORCA_STRUCTURED_SESSION so the unbound gate-create case deterministically hits throwNoActiveSenderTerminal, and updates the assertion to the new string.

️ The SSH fallback still ships the old, unqualified advice

src/main/ssh/ssh-remote-orchestration-send.ts:29-31 carries a byte-identical copy of the message this PR just fixed: Pass --from <terminal-handle> or run the command inside a live Orca terminal with ORCA_TERMINAL_HANDLE set. It fires on the same lifecycle-send path (worker_done / heartbeat with no explicit or env handle) when the request is served through the legacy SSH bridge, so the exact misleading advice this PR removes from the local CLI survives on the SSH execution path. AGENTS.md asks that every change consider the SSH use case, and here the two strings are twins that should move together.

Technical details
# SSH fallback keeps the pre-fix refusal copy

## Affected sites
- `src/main/ssh/ssh-remote-orchestration-send.ts:29-31` — `resolveRemoteOrchestrationSender` throws `RemoteCliArgumentError('no_active_sender_terminal', 'Could not determine the sender terminal for this orchestration command. Pass --from <terminal-handle> or run the command inside a live Orca terminal with ORCA_TERMINAL_HANDLE set.')`. Reached via `ssh-remote-orca-cli.ts:211` for `orchestration send`.
- `src/cli/handlers/orchestration/terminal-identity.ts:198-200` — the local-CLI twin, updated by this PR.

## Required outcome
- An identity-less lifecycle send over the SSH bridge must not suggest `--from <terminal-handle>` without the same qualifier the local CLI now carries (or points at the same direct-drive remediation).

## Suggested approach (optional)
- Mirror the updated `throwNoActiveSenderTerminal` wording in `resolveRemoteOrchestrationSender`, or extract the string into one shared constant so the two copies cannot drift again. Its test (`ssh-remote-orchestration-send.test.ts:26-30`) asserts only the error code, so either way stays green.

ℹ️ Nitpicks

  • The sentence every live handle belongs to a different pane, whose mailbox passing it would consume (terminal-identity.ts:44-45) is hard to parse because the relative clause splits whose mailbox from passing it would consume. Something like — every live handle names a different pane, and passing it would consume that pane's mailbox says the same thing in one pass.

Pullfrog  | Fix it ➔ | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

@coderabbitai

coderabbitai Bot commented Sep 16, 2026 •

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: f67a7f2d-ac57-4b9c-8738-a15f6f104e9b

📥 Commits

Reviewing files that changed from the base of the PR and between ce4f4da and f70ed8c.

📒 Files selected for processing (3)
  • src/cli/handlers/orchestration.test.ts
  • src/cli/handlers/orchestration/terminal-identity.ts
  • src/main/ssh/ssh-remote-orchestration-send.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 1 remains after this review.


📝 Walkthrough

Walkthrough

The CLI now provides specific guidance for missing terminal identity and for sender handles that belong to another pane. Structured-session errors now direct workers to launch through a worktree with --agent. The orchestration tests clear and restore ORCA_STRUCTURED_SESSION and verify the structured-session refusal behavior.

Priority: ⬇️ Low

Merge Risk: ⚪ Minimal · up to f70ed

The revised refusal guidance remains appropriate for CLI users and introduces no actionable merge risk.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description clearly explains the problem, user impact, implementation, testing, and visual-proof status. However, it omits the required Linked Issue entry and does not include the required checkli… Add the linked issue after Fixes #. Complete or explicitly address the required AI Disclosure, Review, Agent skill upstream boundary, Notes, and Checklist sections.
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 6 functions across 4 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: correcting sender-terminal refusal guidance for orchestration.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Description check

Explanation

The description clearly explains the problem, user impact, implementation, testing, and visual-proof status. However, it omits the required Linked Issue entry and does not include the required checklist sections.

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 05015c51-b910-4cdd-9c07-99ca78d8e303

📥 Commits

Reviewing files that changed from the base of the PR and between 85d1ffc and ce4f4da.

📒 Files selected for processing (2)
  • src/cli/handlers/orchestration-gate-cli.test.ts
  • src/cli/handlers/orchestration/terminal-identity.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 8 remain after this review.

…d message

`orchestration send --type worker_done|heartbeat` refuses in the send handler before
`resolveOrchestrationTerminalHandle` runs, so the structured guard never saw the case a
structured session hits most: the canonical worker lifecycle report. That caller was still
told to pass `--from` with "your own terminal's handle" — which it does not have, so any
handle it picked would belong to another pane.

`throwNoActiveSenderTerminal` now derives which refusal fits instead of each call site
deciding: marker set AND no handle means no identity exists, so the structured refusal
applies. A stale `ORCA_TERMINAL_HANDLE` is deliberately excluded — that caller does have an
identity, it just went stale, and keeps the advice to re-run under a live one.

Also corrects the guidance itself (`--agent` is a `worktree create` flag; `terminal create`
has no such flag), aligns the SSH fallback wording with its local twin, and pins
ORCA_STRUCTURED_SESSION in the send tests, which until now decided which refusal they
exercised from ambient environment.
@brennanb2025

Copy link
Copy Markdown
Contributor Author

Follow-up commit f70ed8c8d6 — what changed and why.

The gap this closes

The first commit fixed the refusal inside resolveOrchestrationTerminalHandle. But orchestration send --type worker_done and --type heartbeat never reach that function: message-send-handler.ts refuses earlier, as soon as it sees no --from and no ORCA_TERMINAL_HANDLE. That earlier refusal is the generic one, so a chat session with no identity of its own was still told to "pass --from with your own terminal's handle" — a handle it does not have. Any handle it could pick belongs to a different pane, and the next check on that pane consumes mail its rightful owner never sees.

That is the same defect this PR set out to remove, on the path a worker takes most often.

The change

throwNoActiveSenderTerminal now works out which refusal fits, instead of each call site deciding. When the structured-session marker is set and there is no terminal handle at all, no identity exists, so the caller gets the refusal that names no flag to pass.

A stale ORCA_TERMINAL_HANDLE is deliberately excluded. That caller does have an identity — it just went stale — so it keeps the existing advice to re-run under a live terminal. Folding that case in breaks the stale-handle guarantee; it did, until the condition was narrowed.

Three smaller corrections

  • The guidance text told callers to "create a terminal in it with an agent". terminal create has no --agent flag — it takes --command. --agent belongs to worktree create, which launches the agent in the worktree's first terminal. The wording now matches flags that exist.
  • A // Why ... comment used ${flagName} inside a line comment, where it renders literally rather than interpolating.
  • The SSH fallback in ssh-remote-orchestration-send.ts still carried the old unqualified wording; it now matches its local counterpart.

Tests

Added a test pinning the property this PR is about: a structured session's refusal must not name a handle flag. Nothing covered that before, so a regression could have restored the old text with the suite still green. Removing the fix fails that test and only that test.

Also pinned ORCA_STRUCTURED_SESSION in the send tests. They previously inherited it from whoever ran them, which silently decided which of the two refusals they exercised.

Validation

  • Full SSH suite: 2224 passed, 18 skipped
  • Full CLI suite: 1316 passed
  • Refusal-related files run twice, with the marker set and unset: 95/95 both ways
  • pnpm tc:cli, pnpm tc:node: clean
  • pnpm run check:code-quality:changed: 0 findings

Known gap, not addressed here

The SSH path has no structured-session check at all, because ORCA_STRUCTURED_SESSION is not among the variables allowed to cross hosts. Making it cross is a wire-compatibility decision rather than a wording fix, so it is left for a follow-up.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes

The follow-up commit f70ed8c addresses the prior review and adds structured-session routing for lifecycle sends.

  • SSH twin updated (ssh-remote-orchestration-send.ts) — the legacy bridge now emits the same qualified "your own terminal's handle" wording as the local CLI, closing the drift the prior review flagged.
  • Structured refusal extracted and routed (terminal-identity.ts) — structuredSessionRefusal() centralizes the "no handle names this session" copy, and throwNoActiveSenderTerminal() now returns it for an identity-less structured session with no ORCA_TERMINAL_HANDLE instead of advising --from.
  • Regression coverage added — a new case pins that a structured lifecycle send does not recommend Pass --from, and ORCA_STRUCTURED_SESSION is now pinned in the affected suites so the branch they exercise no longer depends on the runner's ambient environment.
  • Prior nitpick resolved — the hard-to-parse relative clause was reworded ("every live handle belongs to a different pane, and passing one would consume that pane's mailbox").

ℹ️ The PR description predates the second commit

The body's "What changed" still says throwNoActiveSenderTerminal "keeps suggesting --from", and neither it nor the "No behavior change" line mentions f70ed8c's env-conditional routing or the SSH twin update. The code is correct; the description just undersells the delta, and a reader could mistakenly believe the SSH path still ships the old advice. Worth a quick refresh.

Technical details
# Stale PR description

## Affected sites
- PR body, "## What changed" / "## The bug" — written against `ce4f4da`, before `f70ed8c` updated
  `src/main/ssh/ssh-remote-orchestration-send.ts:29-31` and added the structured branch in
  `throwNoActiveSenderTerminal` (`src/cli/handlers/orchestration/terminal-identity.ts:207-209`).

## Required outcome
- Description should mention the SSH copy fix and that the lifecycle-send refusal now routes to
  the structured message for identity-less structured sessions.

Pullfrog  | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant