[FEATURE] Projects (beta): send messages into a Project by name or ID, with sender identity

Status Open
Maintainer reply None cached
Activity 0 comments · opened Oct 9, 2026

Written by Claude Opus 5.5 via Claude Code for the Claude Code maintainers; I prompted it, it did the work, I read it.

Feature category: CLI commands and flags / Projects (cowork)
Priority: Medium. Workarounds exist, but they are fragile.

Problem statement

You can't address a claude.ai Project from outside the Project UI. Local Claude Code, scripts, and other tools can reach individual cloud sessions, but not the Project itself.

What works today, verified 2026-10-08 on Claude Code (Fedora) and Desktop 2.26454.2:

  • claude -p "<msg>" --cloud <session_id> delivers a message to one thread session of a Project. It arrives as a plain user turn, with no project envelope and no information about who sent it. The receiving Claude can't tell whether a person typed it, a script sent it, or another agent wrote it.
  • From inside a thread, send_message reaches a local Remote Control session as a <cross-session-message>. The local session has no matching tool to answer back.

What doesn't work:

  1. There's no stable address for the Project. The project-chat (coordinator) session is recycled routinely, and its session ID changes. We saw it change within a single day. Anything that stores the ID breaks silently.
  2. You can't address a Project by name or ID. chan_… IDs are visible in URLs, but no CLI flag or API takes one.
  3. The sender isn't identified. Messages sent with --cloud look the same as a person typing in the session. A Project with several members, or one that receives automated messages, needs to know who or what sent each message, and how much to trust it.
  4. Messages aren't visible in the Project. A --cloud message lands in the session transcript. It doesn't appear in the project chat or thread UI as a message other members can see, react to, or reply to.

Proposed solution

Addressing

claude project send <project>            "<msg>"   # posts to the project chat
claude project send <project> --thread <thread>  "<msg>"   # posts into an existing thread
claude project send <project> --new-thread "<title>" "<msg>"  # starts a thread
claude project list                                 # name, id, threads (for scripting)
  • <project> accepts the Project ID (chan_…) or a unique name. An ambiguous name is an error that lists the candidates.
  • <thread> accepts the thread's root message ID (cmsg_…) or its title.
  • Messages go to the project-chat endpoint, not to a session ID. The server routes them to whichever coordinator session is currently live, or queues them if none is. Session recycling no longer breaks senders.
  • Messages sent this way show up in the project chat or thread as normal messages, with the same wake/notify behaviour as a message typed in the UI.

Sender identity and trust

  • Each message carries an authenticated sender: the claude.ai account, plus a source label such as cli, local-session:<name>, or api-key:<label>. It renders in the UI (for example "Ronny · via local Claude Code (homesetup)").
  • On the agent side, it arrives in the existing <wake> envelope with from/author set and a source attribute. A message from a person's own CLI could get trust="principal", and a message from automation trust="relay". Whichever mapping is chosen, the receiving Claude gets the information and doesn't have to guess.
  • Optional: --as-agent marks the message as written by an agent (a local Claude session), so the Project treats it like a peer message rather than a user instruction.

Replies back to the sender (optional, phase 2)

  • --wait blocks until the Project or thread posts a reply, then prints it, with a timeout.
  • Alternatively, a local session started with --name gets a reply address, so the project-side Claude can send_message back. That closes the current gap where local sessions can receive but not answer.

API

Expose the same thing as an HTTP endpoint, POST /v1/projects/{id}/messages (plus a thread variant), authenticated with the account's OAuth token or an API key with project scope. That covers cron jobs, CI, home-automation hooks, and phone shortcuts.

Alternative solutions considered

  • Post to the coordinator session ID with --cloud: breaks when the session recycles, and the sender is missing.
  • A dedicated "inbox" thread whose ID is kept stable by hand: extra state to maintain, and messages still arrive without a sender.
  • A shared file or folder plus a manual "go read it" from the user: this is our current workaround (a handoff folder on the laptop that threads reach through the Desktop device link). It works, but a person has to start every pickup.
  • An artifact database as a mailbox: possible, but polling-based and not visible in the Project.

Use case example

  1. In a local Claude Code session on my laptop, I finish a plan and run claude project send homelab "handoff: AgentHandoffs/2026-10-08-foo.md is ready, please pick it up".
  2. The message appears in the project chat as "Ronny via local Claude Code". The coordinator starts a thread, and the thread reads the file through the device link.
  3. Later, a cron job on my server posts claude project send homelab --thread "nightly backups" "backup failed: …". It lands in that thread labelled as automation, and the thread Claude investigates.

Additional context

  • Related, but not covering this: #98369 (sending messages into local desktop Code sessions), #77932 (cross-session messaging, shipped; replies only across machines, Projects not covered), #53049 and #24983 (injecting external events into local sessions), #99156 (local sessions as Project threads). See also https://github.com/anthropics/claude-code/issues/100715 (local pseudothreads).
  • Today the only working route is a raw session ID. It's undocumented for Projects, and it loses the sender.

View original on GitHub ↗