A lower-mode thread can't report back to the thread that launched it, and the refused send is invisible#26293

opened4d agobyEEtanHey

t3_thread_send refuses any target whose runtime mode is above the caller's (writesThreads -> assertTargetWithinLimits, and sendToThread re-checks with resolveRuntimeMode; added in #16335). For a send that starts, steers, queues or restarts a turn, that's right: otherwise an Auto thread could make a Full-access agent act on text it chose.

It also blocks the common reverse case: a Full-access thread launches a narrower one with t3_thread_launch (for example an Auto-mode thread running a security scan), and the narrower thread can't tell its launcher that it's done:

1{"code":"runtime_mode_escalation_denied","message":"Child runtime mode full-access is broader than parent mode auto."}

Two problems:

  1. No upward path. A delegate_task child already reports into its parent: its completion result wakes the parent, because the parent chose to start it. A thread started with t3_thread_launch records no launcher, so it has no equivalent. The only workarounds are out-of-band (write a file and have the launcher watch it, or the launcher blocks in t3_thread_wait).
  2. The refusal is silent. The command records nothing (by design in #16335), so neither the target nor the launcher learns a report was attempted. Only the sending agent sees the error, and it can't escalate. The error message also says "Child runtime mode" when the target isn't a child of the sender.

Possible shapes (any one would do):

  • Record the launcher on t3_thread_launch and let a launched thread report to its launcher, the way delegate completions already do. The message could arrive as a completion-style notification rather than a user turn.
  • A non-driving notify send: it shows in the target thread and marks it unread or wakes it like a task notification, but never starts, steers or restarts a turn with the sender's text as user input.
  • At minimum: when a send is refused for mode, leave a visible note on the target ("a message from was refused: runtime mode"), and use a send-specific error message.

Repro: from a Full-access thread, t3_thread_launch a thread with runtimeMode: "auto" whose prompt ends with t3_thread_send to the launcher's thread id. The send returns runtime_mode_escalation_denied and the launcher never hears.

Seen on a current nightly; the check is unchanged on main at 0c80751701. Related to #25288 (delegate session release) only in that both affect how launched/delegated threads report back.

Synced from GitHub

Rechecked on 2026-10-28 through T3 Code's built-in triage playbook with Codex / GPT-6.1 Sol.

Both Macs currently answer live MCP calls at 0.1.4-nightly.20261028.3814. Linked-environment discovery and a remote t3_environment_read succeed. That verifies the multi-machine connection, but does not provide the lower-mode reporting path requested here.

At the exact installed tag (ce66aab13fabde01b82deac4b4b3eaae53641988), OrchestratorMcpService.sendToThread still calls resolveRuntimeMode(limits.runtimeMode, target.thread.runtimeMode) before dispatch. The same check remains in the latest published nightly, 0.1.4-nightly.20261028.3824 (eb751fa0730d6a51d56547cb7fcaf4822a7e0e3b). Its MCP-service changes concern catalog search and cross-project listing, not this check. delegatedFrom is transfer provenance, not an automatic completion-report channel for ordinary launched threads.

Keeping this open. This is an exact-tag source recheck plus live connection verification, not a fresh lower-mode-send reproduction or a live .3824 test.

Synced from GitHub

Confirmed on Windows with a real delegated child, not only an independent t3_thread_launch thread.

The parent runs in full-access, and the server records the child with runtimeMode: "auto", relationshipToParent: "subagent", and the correct parentThreadId. The parent explicitly instructed it to send questions back with t3_thread_send. Its actual call failed:

json
1{"_tag":"OrchestratorMcpFailure","code":"runtime_mode_escalation_denied","message":"Child runtime mode full-access is broader than parent mode auto."}

The parent still receives automatic task-completion notifications, but the child cannot send questions or intermediate reports. Another active delegated child records the same refusal. The live server reports 0.1.4-nightly.20261028.3841.fork.1; the unconditional mode check is also present in upstream main at 575579d68a.

Minimal reproduction:

  1. Start a full-access parent thread.
  2. Delegate a task with runtimeMode: "auto".
  3. From the live child, call t3_thread_send with its recorded parent's ID and mode: "auto" or "queue".
  4. Observe the error above instead of delivery.

A narrow fix is prepared at Alb11747/t3code@70910718d2, on fix/subagent-parent-messages (comparison against upstream main). It allows normal/queued reports only from an auto-mode subagent to its server-recorded direct parent. It keeps the interaction-mode ceiling, live-caller and linked-environment checks, and rechecks sender identity and the recorded relationship during command dispatch. It does not exempt unrelated threads, forks, interrupt/configuration/approval actions, explicit steering or restart requests. Slash-command messages retain the normal ceiling as well: otherwise a child could replace its parent's native /goal without the parent interpreting the report.

The integrated regression fails on unmodified upstream main and passes with the fix. It exercises actual MCP authorization, the service, and durable in-memory orchestration; no paid provider turn is needed. Reports persist with the child's sender identity, modes remain unchanged, and unrelated targets, forks, native slash commands, restart/steer/interrupt, plan-to-default sends, and stale callers remain denied.

This addresses the delegated-child case. It does not add launcher provenance or an upward reporting channel for independent t3_thread_launch threads.

vp run check:changed upstream/main passed formatting, lint, both knip checks, GraphQL generation, affected-package typechecks, and 133 tests in 9 files. Independent permission-boundary review was clean after the slash-command restriction. Full release CI and deployment are still pending; these are local validation results, not a live post-update reproduction. The prepared fix is available for maintainers to consider; maintainers can choose another approach.

Investigated with GPT-6.1 Sol through the Codex harness in T3 Code.