relay-node: sessions can create a channel (POST /channels, subscribe-channel --create)
An agent on the session socket could join a channel but not create one: subscribing to a missing channel returned 404 channel_not_found, the probe's socket-request allowlist refused any create attempt ("Unsupported relay socket request"), and the core router had no create route. Kenny Stone hit this asking an agent to join channel "graph".
What changes
- New session-socket route
POST /channels: takes{"channel","topic"}as a JSON body or?channel=&topic=in the query. The name is validated as subscribe does; the topic is optional, trimmed, at most 500 characters, no control characters. Same sender check and rate gate as subscribe, then create-then-join through the SDK'sensure_joined_channel. A channel that already exists is joined, not refused, so the call is idempotent. Response:{"from","channel","created":bool,"subscribed":true}; Relaycast errors keep their status and code. client::create_channelreturns whether this call created the channel;ensure_channelis unchanged.- The route is in
SESSION_ROUTES, the not_found hint and the probe's socket-request allowlist. - CLI:
subscribe-channel <channel> --create [--topic <text>]sendsPOST /channels. The subcommand keeps its name soALLOWED_SUBCOMMANDS, the Codex sandbox rule and the Claude permission rule stay as they are.--createconflicts with--remove;--topicrequires--create. - Hints: a subscribe refused with
channel_not_foundnow says to pass--create; the session preamble mentions--create(orPOST /channelsfor the curl form).
Scoping
Checked with layered-control: Relaycast POST /v1/channels accepts agent tokens (requireAuth any=['workspace','agent']), the hosted MCP subscribe_relay_channel already takes create: true, and Control issues no Relaycast scopes. The only gap was the relay-node session socket, which this PR covers. No Control or webapp change.
Note for review
Because the subcommand name is unchanged, the existing Codex sandbox and Claude permission rules for subscribe-channel now also let a session create a channel without a prompt. The sandbox-reach test names POST /channels and the topic key as the routes and keys that rule reaches.
Tests
cargo fmt --all -- --checkclean.- Unit groups
relay::outbound::channels relay::session_cli sandbox_rule_reach_tests relay::cli::tests session_routes_list_every_route relay::hosted_mcp relay::codex::stop_hook relay::claude::session_hook::permissions relay::delivery::channel: 106 passed. tests/relay_outbound.rs: 114 passed, including the newa_session_creates_a_missing_channel(404 hint, create with topic, idempotent query-form repeat, existing channel joined, bad name/topic 400 with nothing sent, unregistered session 403).cargo clippy --testsstops on a pre-existingunused_io_amounterror insrc/relay/sdk_http.rs:147, untouched here.
No Desktop (macOS/GTK) surface shows channel creation, so there is no parity change.
🤖 Generated with Claude Code
![Autofix with [code]smith](https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark-3x.png)
Open in [code]smith · Need help on this PR? Tag @codesmith-bot with what you need.