Skip to main content

Claude Code remote dispatch

@flow-state-dev/claude-code/cli — dispatch a Claude Code cloud task from inside a flow by shelling out to your local claude CLI. You get back a handle, not a live stream.

When to use it

Reach for this when a flow needs to hand a coding task to a cloud agent and keep working: a long autonomous refactor, a build-and-fix loop, or an orchestrator that dispatches work and references it later. It is how flow-state-dev triggers the same claude --remote tasks the team runs by hand.

It is deliberately not the bash tool. The bash tool runs commands in a bounded sandbox. This runs your host machine's already-authenticated claude CLI — a different, trusted, opt-in trust model.

It is fire-and-forget. The CLI has no headless way to poll or stream a cloud task's progress yet, so the block dispatches and returns. Watch progress with /tasks in the CLI, on claude.ai, or from the mobile app.

How it works

claude --remote "<instructions>" creates a cloud session on claude.ai. The cloud VM clones your repository's GitHub remote at your current branch, so push local commits first — the VM clones from GitHub, not your working tree. The block parses the session URL from the CLI output, persists a handle, emits a status item, and returns the handle.

Opt-in and trust

A capability is a reusable bundle you attach to a block. Installing this one is the host's explicit statement that the process may run claude; nothing shells out otherwise.

import { generator } from "@flow-state-dev/core";
import { createClaudeCliCapability } from "@flow-state-dev/claude-code/cli";

const planner = generator({
name: "planner",
model: "openai/gpt-5.4-mini",
uses: [createClaudeCliCapability()],
});

resolveClaudeCli is the seam that supplies the binary path, working directory, and environment. The default resolves claude from PATH; pass your own to point at a specific binary or to mock the subprocess in tests.

Dispatch as a sequencer step

When the host (not the model) decides to dispatch, use the handler directly:

import { claudeRemoteDispatch } from "@flow-state-dev/claude-code/cli";

const dispatch = claudeRemoteDispatch({ cwd: "/path/to/repo" });
// seq.step(dispatch) with input { instructions: "Execute docs/migration-plan.md" }

Session state

Each dispatch appends a handle to claudeRemoteTasks in session state, so a later request can reference it:

FieldMeaning
sourceAlways "cli-remote".
statusAlways "dispatched" in this version.
sessionIdCloud session id, when found in CLI output.
urlclaude.ai session URL, when found.
instructionsThe dispatched instruction string.
rawVerbatim CLI stdout, kept so nothing is lost if parsing misses.
dispatchedAtEpoch millis.
const tasks = ctx.session.state.claudeRemoteTasks ?? [];

Error handling

SituationBehavior
claude binary can't be launchedThrows ClaudeCliNotFoundError. Install the CLI or fix the resolver.
Wrong auth (API key, not claude.ai)Non-zero exit → ClaudeRemoteDispatchError carrying stderr.
Dispatch call exceeds the timeoutAborted → ClaudeRemoteDispatchError.
Exit 0 but no URL in outputTreated as dispatched: handle with url: null, raw output retained, a warning status item. No throw.
Empty instructionsClaudeRemoteDispatchError before anything runs.

Limitations

No headless progress polling or streaming today — that waits on the CLI exposing a machine-readable cloud-task status surface. The persisted handle and its status field are shaped to absorb that follow-up without a breaking change.

See also: Tools overview, and the CLI for running flows locally rather than dispatching to the cloud.