[FEATURE] Projects (beta): send messages into a Project by name or ID, with sender identity
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_messagereaches a local Remote Control session as a<cross-session-message>. The local session has no matching tool to answer back.
What doesn't work:
- 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.
- You can't address a Project by name or ID.
chan_…IDs are visible in URLs, but no CLI flag or API takes one. - The sender isn't identified. Messages sent with
--cloudlook 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. - Messages aren't visible in the Project. A
--cloudmessage 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>, orapi-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 withfrom/authorset and a source attribute. A message from a person's own CLI could gettrust="principal", and a message from automationtrust="relay". Whichever mapping is chosen, the receiving Claude gets the information and doesn't have to guess. - Optional:
--as-agentmarks 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)
--waitblocks until the Project or thread posts a reply, then prints it, with a timeout.- Alternatively, a local session started with
--namegets a reply address, so the project-side Claude cansend_messageback. 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
- 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". - 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.
- 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.