Skip to content

[Bug]: An agent's message to another thread runs any skill it mentions, as if the user typed it #24667

Open

Description

@offchan42

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Area

apps/server

Steps to reproduce

  1. From any thread, call the MCP tool t3_thread_send to a Claude thread with:
    PREFIX: the literal mention $technical-writing appears mid-sentence here. SUFFIX: report the exact text you received, then stop.
    
  2. Send the same text to a Codex thread.
  3. Send each thread a message that starts with the skill as a slash command instead:
    /technical-writing PREFIX: report the exact text you received, then stop.
    

Any installed skill works; we used technical-writing because it changes nothing.

Expected behavior

Text that one agent sends another arrives as text. A skill mention in it does not invoke the skill, because the recipient's human never asked for that. An agent that means to invoke a skill in another thread says so explicitly.

Actual behavior

Message from an agent Claude thread Codex thread
$technical-writing mid-sentence the skill's full SKILL.md is added to the turn, framed as a /technical-writing command the user ran the skill's full SKILL.md is added to the turn in a skill wrapper
Starts with /technical-writing same as above nothing added

The message text itself is kept; nothing is replaced. In the Claude thread the mention is rewritten to /technical-writing, and the command arguments hold only the text after it, so they drop everything before the mention. The recipient agent then reads that its user ran the skill on those arguments. Nothing tells it that another agent wrote the mention.

Source at eabcfa2 (main) matches this:

  • OrchestratorMcpService.ts records createdBy: agent and senderThreadId for the message, then passes its text on unchanged.
  • ClaudeSkillDispatch.ts (planClaudeSkillDispatch) turns any known $name mention into a native slash command, and ClaudeAdapterV2.ts sends it as user-role content, on both the start-turn and steer paths.
  • CodexAdapterV2.ts sends the text as plain input, and Codex invokes the mentioned skill itself.
  • Neither path uses the agent origin. scheduledTasks/webhookTemplate.ts already neutralizes mention and command syntax in untrusted webhook values, which looks like the boundary this case needs too.

Impact

Major degradation or frequent failure

Many skills treat an explicit invocation as the user's authorization to act. When agents message each other, as a coordinator relaying decisions or a reviewer reporting, a message that only names a skill (for example "the release skill reported X") starts that skill in the recipient. The recipient sees a command it cannot tell from its own user's. We saw it only with a read-only skill, and no tool ran.

Version or commit

T3 Code 0.1.3-nightly.20261024.3643; source read at eabcfa2 (2026-10-25)

Environment

Windows 11 x64; Claude Code (Claude Opus 5.5) and Codex (gpt-6-astra) threads

Workaround

Senders avoid writing skills as $name or a leading /name in messages to other threads.

Activity

  1. offchan42 commented on Oct 25, 2026

    @offchan42
    Author

    Addendum: the mention detector accepts any currency sign, not only $, so the workaround above ("avoid $name") is incomplete.

    At c79ef8c (main), apps/server/src/provider/Drivers/ClaudeSkillDispatch.ts:

    // lines 10-11
    const SKILL_MENTION_PATTERN =
      /(^|\s)\p{Sc}(?![0-9][0-9_]*(?:[kKmMbBtT]|[eE][0-9]+)?(?:\s|$))(?=[a-zA-Z0-9:_-]*[a-zA-Z])([a-zA-Z0-9][a-zA-Z0-9:_-]*)(?=\s|$)/gu;

    \p{Sc} is any Unicode currency symbol, and ClaudeSkillDispatch.test.ts (lines 43-60) dispatches mentions prefixed with € £ ¥ ₹ ₩ ₿ 𑿝. Amounts such as €20 or $20k are excluded by the lookahead. So in a Claude thread, an agent's message containing €technical-writing should dispatch the skill exactly as $technical-writing does. This follows from the source and its tests; we reproduced it live only with $, and we did not test other signs in Codex threads.

    For the fix: scheduledTasks/webhookTemplate.ts already breaks these mentions in untrusted webhook values (line 52, /[\p{Sc}@]|(?<=^|\s)\/(?=[^\s/]+(?:\s|$))|.../giu, followed by a U+2060 word joiner at line 170). Applying the same treatment to agent-originated t3_thread_send text would cover every currency sign and a leading /name.

    On our side, a change accepted today makes sitrep, the cross-session messaging tool we use, push any message containing a currency sign as a neutral pointer. The recipient reads the exact text through its hook or by asking sitrep. That only covers our own sender; other agents calling t3_thread_send directly are still affected.

  2. royalaid commented on Nov 3, 2026

    @royalaid

    Another entry point for the same root cause, which the open fix (#24778) does not cover: delegated-task completion wakes.

    When a delegate_task child finishes, the server inlines the child's result into the parent's wake message (delegatedCompletionWakeDetail). The Claude adapter sends that wake like any user turn, with the user-invocable skill map (L10754, L11203), so planClaudeSkillDispatch turns any $name in the child's output into a native skill command. #24778 neutralizes mentions only in t3_thread_send, and its description says delegation flows keep their current behavior.

    What happened. A research child mined old transcripts and returned JSONL. One row quoted an earlier user prompt containing $reprompt, which is an installed skill. The Claude parent's next turn ran /reprompt with the rest of the child's output as its arguments, and the skill loaded as if the user had invoked it. The parent noticed and ignored it; nothing in the turn showed that a child wrote the mention.

    The outgoing prompt.offer in the provider event log shows the split (redacted, lengths are the real ones):

    content[0].text (9809 chars): Delegated task <task-id> (mine routing cases: <host> (retry)) [completed]:
      {..."quote":"... have it /reprompt itself, then have it tee it up for me",...}
    content[1].text (9119 chars): /reprompt itself, then have it tee it up for me","context":...}
    

    content[1] is the tail of content[0] from the mention on. The child's own text had $reprompt; T3 rewrote it.

    Still on the newest nightly. ClaudeSkillDispatch.ts has not changed since the incident build. Running planClaudeSkillDispatch from v0.1.7-nightly.20261103.4119 on a wake-shaped string, Delegated task t1 [completed]:\n{"quote":"have it $reprompt itself, then tee it up"}, with reprompt in the skill map returns commandText = /reprompt itself, then tee it up"}.

    Version: incident on 0.1.4-nightly.20261028.3824 (Linux x64 server, Claude Code 2.1.312, Opus 5.5 parent, Codex child on a linked environment). Source read at v0.1.7-nightly.20261103.4119 (cefb544).

    A fix at the wake boundary, such as the webhookTemplate.ts mention treatment, would cover this path. Question relays and other server-built messages that embed agent output may need the same treatment. I did not check Codex parents, which per the report above expand $name themselves.

    Filed by: Claude Opus 5.5 (Claude Code), on behalf of the poster, who reviewed it before posting.

  3. added 3 commits that reference this issue on Nov 12, 2026
    6eb4ae3
    4e07518
    6e950f6
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