Skip to content

[Bug]: Artifact comment auto-replies never arm in Claude threads: every composer message reaches Claude Code as turnOrigin "sdk" #24131

Open

Description

@rolfilho

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

Prerequisites: Claude Code logged in with a claude.ai account, "env": {"CLAUDE_CODE_ARTIFACT": "1"} in ~/.claude/settings.json (see #14816), and a claude.ai artifact you own. Any artifact works; ask Claude to publish a one-line HTML page if you have none.

  1. In a T3 Claude thread, send: Watch https://claude.ai/artifact/<id>. Claude calls ArtifactComments with action: "watch". The watch connects, and the result says auto-replies are not armed.
  2. On the artifact page, leave a comment and use Send to Claude. The T3 thread does not wake.
  3. Confirm the turn origin. Find the thread's transcript (the newest .jsonl under ~/.claude/projects/<project-dir>/) and run:
    jq -c 'select(.type=="user" and (.message.content|type)=="string") | {entrypoint, turnOrigin, text: .message.content[0:40]}' <transcript>.jsonl
    Every message typed in the T3 composer shows "turnOrigin": "sdk".
  4. Ask Claude to stop the watch (on: false), then send Watch https://claude.ai/artifact/<id> again. The watch is refused for the rest of the session.
  5. Control: in a terminal, run claude in the same project and send the same Watch message. Auto-replies arm, and a comment sent to Claude gets a reply.

Expected behavior

The watch arms comment auto-replies, as it does in a terminal claude session. A comment sent to Claude wakes the thread, and Claude replies in the comment thread.

Actual behavior

The watch connects, but auto-replies never arm. The tool result says:

auto-replies arm only when the user asks for the watch in their own message, and no message from the user started this turn

A comment sent to Claude does not wake the thread.

Control: on the same machine, with the same settings and the same artifact, a terminal claude session armed auto-replies on a typed Watch <url>. The first comment sent to Claude got an auto-reply.

After one stop, every later watch request in that session is refused, including one I typed myself in the composer:

Not watching: watching this artifact was stopped earlier in this session, and this request did not come in a turn this session can verify the user started (a task notification, a wakeup, or a stop receipt started it — or a typed /command, whose origin is not recorded).

Likely cause

Claude Code records every user message T3 sends with turnOrigin: "sdk". Claude Code's artifact watch arms auto-replies only on a turn it can verify the user started, and an sdk turn does not qualify. The session transcript shows it for all three of my messages:

{"entrypoint": "sdk-ts", "turnOrigin": "sdk", "promptId": "<uuid>", "version": "2.1.309"}  "Monitor https://claude.ai/artifact/..."
{"entrypoint": "sdk-ts", "turnOrigin": "sdk", "promptId": "<uuid>", "version": "2.1.309"}  "help me fix this"
{"entrypoint": "sdk-ts", "turnOrigin": "sdk", "promptId": "<uuid>", "version": "2.1.309"}  "Watch https://claude.ai/artifact/..."

The gate lives in Claude Code, not in T3. I don't know whether the Agent SDK lets a host mark a message as typed by the user. If it does, T3 should set that on composer messages. If it doesn't, this needs an upstream request to Anthropic, and a note in T3 that comment auto-replies don't work in T3 threads.

Related: #13978 (the same watcher shows in the Agents panel as a working agent) and #14816 (Artifact tool enabled by default in Claude threads).

Impact

Major degradation or frequent failure

Version or commit

T3 Code (Beta) 0.1.2

Environment

macOS 26.6.2, desktop app; Claude Code 2.1.309 (entrypoint sdk-ts), Opus 5.5, auto permission mode

Workaround

Don't stop a watch in a T3 thread: after a stop, the session can't restart it. Ask Claude to read the comments on demand instead of relying on auto-replies, or run the watch from a terminal claude session.

Activity

  1. rolfilho commented on Oct 23, 2026

    @rolfilho
    Author

    Follow-up: an SDK host can mark a message as human-typed. anthropics/claude-code#102828 shows Claude Desktop messages recorded with origin: {"kind":"human"}, turnOrigin: "human", promptSource: "sdk". T3 composer messages in my transcript show origin: null, turnOrigin: "sdk", promptSource: "sdk". So the likely fix is in T3: set the human origin on messages typed in the composer, and keep sdk for automated sends (wakeups, scheduled tasks, orchestration messages).

  2. samwang0041-star commented on Oct 31, 2026

    @samwang0041-star

    The same turnOrigin: "sdk" gate also suppresses Claude Code's ultracode keyword opt-in in T3 threads, not just artifact auto-replies.

    Repro on T3 Code 0.1.6 (Beta) + Claude Code 2.1.316, with enableWorkflows: true and the default workflowKeywordTriggerEnabled:

    1. In a T3 Claude thread, send a message containing the word ultracode (standalone or inside a sentence).
    2. The thread's transcript has no workflow_keyword_request attachment, so the model never gets the "opting this turn into multi-agent orchestration — use the Workflow tool" reminder. In my test the first message contained both ultrathink and ultracode: the transcript has an ultrathink_effort attachment but no workflow_keyword_request, and the user message has turnOrigin: "sdk" / promptSource: "sdk".
    3. Control: the same keyword in a terminal claude session (human-typed prompt) emits the workflow_keyword_request reminder and the turn can enter the Workflow tool. ultrathink still works in T3 because its check does not require isHumanTypedPrompt.

    In Claude Code 2.1.316 the keyword trigger is gated as (simplified):

    workflowsAvailable && isHumanTypedPrompt && !suppressWorkflowKeyword
      && (settings.workflowKeywordTriggerEnabled ?? true)

    isHumanTypedPrompt is derived from sessionChannelPersonTyped, separate from the visible ultracode settings key. The xhigh/model-capability gate applies to the persistent ultracode setting and /effort ultracode, not to this keyword path, so this is only about the message provenance.

    Question for #24962: does stamping origin: { kind: "human" } also make sessionChannelPersonTyped true, so the keyword trigger starts working? If yes, a regression like "a human-origin turn containing ultracode emits workflow_keyword_request, while scheduled/MCP turns do not" would lock it in. If not, T3 either needs to pass the session-channel person-typed flag (if the SDK exposes it) or detect the keyword in the adapter/composer itself.

    Practical impact: the missing Ultracode picker entry cannot be worked around by typing the keyword in a T3 thread today. A plain "use a workflow / fan out agents" request still counts as explicit opt-in for the Workflow tool, but the ultracode keyword does not.

  3. sipak commented on Nov 8, 2026

    @sipak

    We patched this in a fork and it works. On Claude Code 2.1.319 the CLI keeps only origin {kind:"human"} from stdin and silently drops other kinds, so it's human or nothing. Our rule: stamp human exactly when createdBy === "user" and creationSource is web or mobile, on both startTurn and steerTurn in ClaudeAdapterV2. Leave t3_thread_send, delegations, continuations, limit recovery and scheduled tasks unset. Typed prompts then record turnOrigin: "human", and agent sends stay "sdk".

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