Skip to content

Channels (Experimental) ​

Experimental

The channels feature relies on a Claude Code server-side feature flag (tengu_harbor) that is not yet generally available. In -p (print/non-interactive) mode — which agentrunner uses — channels will silently fail unless this flag is enabled on your account. Channels work in interactive mode regardless of the flag. A future version will use --input-format stream-json to bypass this limitation. See anthropics/claude-code#24594.

Channels enable two-way communication with a running Claude Code session. While the agent is working, you can send it messages from external sources (CI notifications, webhook events, user input) and receive structured replies.

How It Works (Experimental) ​

When you enable channels, agentrunner:

  1. Starts an agentrunner-channel MCP server alongside the Claude CLI process
  2. Creates a Unix socket for your code to send messages through
  3. Forwards your messages to Claude as MCP channel notifications
  4. Claude can reply using the reply tool, which surfaces as channel_reply messages in the stream
Your code ──send()──► Unix socket ──► MCP server ──► Claude Code
                                                        │
Your code ◄──stream── channel_reply ◄── reply tool ◄────┘

Enabling Channels (Experimental) ​

go
session, err := runner.Start(ctx, "Review this PR",
    claudecode.WithChannelEnabled(),
    agentrunner.WithSkipPermissions(),
)
ts
const session = runner.start("Review this PR", {
  channelEnabled: true,
  skipPermissions: true,
});

Sending Messages (Experimental) ​

Wait for the system init message before sending — this confirms the MCP server is connected.

go
import "github.com/driangle/agentrunner/go/channel"

for msg := range session.Messages {
    if msg.Type == agentrunner.MessageTypeSystem {
        // MCP server is ready — send a message.
        err := session.Send(channel.ChannelMessage{
            Content:    "Build #1234 failed on main",
            SourceID:   "ci-build-1234",
            SourceName: "GitHub Actions",
        })
        break
    }
}
ts
for await (const msg of session.messages) {
  if (msg.type === "system") {
    await session.send({
      content: "Build #1234 failed on main",
      sourceId: "ci-build-1234",
      sourceName: "GitHub Actions",
    });
    break;
  }
}

ChannelMessage Fields ​

FieldTypeDescription
contentstringMessage body that Claude reads
sourceIdstringCaller-defined correlation ID
sourceNamestringHuman-readable origin (e.g. "GitHub Actions")
replyTostring?Optional reference to a prior message's sourceId

Receiving Replies (Experimental) ​

When Claude replies to a channel message, it emits a channel_reply message in the stream:

go
for msg := range session.Messages {
    if msg.Type == agentrunner.MessageTypeChannelReply {
        fmt.Printf("Reply to %s: %s\n",
            msg.ChannelReplyDestination(),
            msg.ChannelReplyContent(),
        )
    }
}
ts
import {
  messageChannelReplyContent,
  messageChannelReplyDestination,
} from "@driangle/agentrunner/claudecode";

for await (const msg of session.messages) {
  if (msg.type === "channel_reply") {
    const content = messageChannelReplyContent(msg);
    const dest = messageChannelReplyDestination(msg);
    console.log(`Reply to ${dest}: ${content}`);
  }
}

Channel Server Logging (Experimental) ​

The channel MCP server runs as a child process of Claude Code. By default it produces no log output. To enable logging for debugging, specify a log file and level:

go
session, err := runner.Start(ctx, "Review this PR",
    claudecode.WithChannelEnabled(),
    claudecode.WithChannelLogFile("/tmp/channel.log"),
    claudecode.WithChannelLogLevel("debug"),
)
ts
const session = runner.start("Review this PR", {
  channelEnabled: true,
  channelLogFile: "/tmp/channel.log",
  channelLogLevel: "debug",
});

Then tail the log file to see the MCP server's activity:

sh
tail -f /tmp/channel.log

Log Levels ​

LevelWhat it logs
debugAll of the below, plus every JSON-RPC request/response and notification delivery confirmations
infoServer start/stop, MCP handshake, channel messages forwarded, tool calls (default)
warnMalformed input, unknown methods/tools
errorFailed writes, unrecoverable errors

What the Logs Show ​

The channel server logs the full lifecycle of a channel session:

  • Server start — socket path being listened on
  • MCP handshake — initialize and initialized messages from Claude Code
  • Channel messages — each message forwarded from your code to Claude, with source_id and content length
  • Tool calls — when Claude invokes the reply tool, with the full arguments
  • Shutdown — clean shutdown or errors

This is useful for diagnosing issues like:

  • The MCP server not starting (binary not found)
  • Messages being sent but not reaching Claude (socket connected but notification not forwarded)
  • Claude receiving messages but not replying (check if reply tool calls appear)

Environment Variables ​

When running the channel binary directly (outside of agentrunner), logging is controlled via environment variables:

VariableDescription
AGENTRUNNER_CHANNEL_LOGFile path to write logs to. No logging when unset.
AGENTRUNNER_CHANNEL_LOG_LEVELLog level: debug, info (default), warn, error.

Known Limitation: Feature Flag in -p Mode ​

The channels feature in Claude Code is gated behind a server-side feature flag (tengu_harbor). In interactive mode, --dangerously-load-development-channels bypasses this gate automatically. However, in -p (print/non-interactive) mode — which is what agentrunner uses — the bypass code path is skipped. This means channels currently only work in -p mode if the feature flag is enabled on your account.

If channel messages are being forwarded by the MCP server (visible in channel logs) but Claude never acts on them, this is likely the cause. You can verify by running Claude in interactive mode with the same flags — if it works interactively but not with -p, the feature flag is not enabled.

This limitation is in Claude Code itself, not in agentrunner. Check for updates to Claude Code that may remove this restriction.

Prerequisites (Experimental) ​

The channel feature requires the agentrunner-channel binary. It is resolved in this order:

  1. AGENTRUNNER_CHANNEL_BIN environment variable
  2. Platform-specific npm optional dependency (TypeScript only)
  3. System PATH
  4. Built from source into user cache (Go only)

Full Example (Experimental) ​

See examples/ts/channel/main.ts for a complete working example that sends a CI notification and prints Claude's reply.