Skip to content

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

Open

Description

@EtanHey

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:

{"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 feat(server): every T3 MCP tool declares who may call it #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 0c80751. Related to #25288 (delegate session release) only in that both affect how launched/delegated
threads report back.

Activity

  1. EtanHey commented on Oct 28, 2026

    @EtanHey
    ContributorAuthor

    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.

  2. Alb11747 commented on Nov 1, 2026

    @Alb11747
    Contributor

    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:

    {"_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.

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions