---
title: command
description: "Make a `command` Effect: an actor slot that runs one program in the Workspace's Sandbox when a state invokes it."
---

# `command`

Function, in `@jr2/orchestrator/src/harness-client.ts`. It is an export of `@jr2/orchestrator`, on the [Authoring surface](../authoring-surface.md) page, in Effects.

## Overload 1

```ts
function command<
  const A extends readonly (string | Arg)[],
  const Opt extends CommandOptions = CommandOptions,
>(
  argv: A,
  options?: Opt,
): EffectLogic<CommandInput<A, Opt["pin"]>, CommandOutput> & OpenOf<EnvOpenParts<Opt>>
```

Make a `command` Effect: an actor slot that runs one program in the Workspace's Sandbox when a
state invokes it. Put it under `actors` and invoke it by its key: `actors: { tests:
command(["node", "--test"]) }`, then `invoke: { src: "tests", onDone: … }`.

`argv` is a list, never shell text: each entry is one argument. An `arg("name")` entry is filled
from the invoke's `input` (`input: { name: "…" }`), as one argument whatever its text. To use a
shell, write it: `["sh", "-c", "…"]`.

Where it runs:
- With no Effect secret in `env`, in the Harness container of the enclosing Workspace's Sandbox.
  It runs in the Worktree of a Repo Slot, or in `/work` in a Workspace with no Repo Slot. The
  Agent's Working tools also run in this container.
- With an Effect secret in `env`, in the Sandbox Effector, against a commit of the branch. This
  is the branch tip when it starts, or with `pin: true` the commit that the invoke's `input.sha`
  names. The output then says `dirty: true` when the Worktree held edits that the command did
  not see.

Every outcome is output for `onDone`, never an error: `{ outcome: "exited", code, tail }`,
`{ outcome: "timeout", tail }`, `{ outcome: "killed", signal, tail }`, or `{ outcome: "unknown",
reason }` when jr2 cannot know what happened. `tail` is the end of the output stream.

After an Orchestrator restart:
- A command with no Effect secret in `env` never runs twice. jr2 attaches to it again, or settles
  `unknown`.
- A command with an Effect secret in `env` runs again when the Sandbox Effector no longer holds
  it. It runs against the same commit. If jr2 has no commit for it, it settles `unknown`.

When the state exits, the command stops: jr2 kills its process group. `onError` is only for a
Machine or config defect.

It refuses, when you call it:
- an empty `argv`: `command(argv): argv is a non-empty list — each entry one argument, never
  shell text`
- an entry that is not a string or `arg()`: `command(argv): an argv entry is a string or
  arg("name"), got <entry>`
- a `timeout` that is not a positive duration: `command: timeout "<timeout>" is not a positive
  duration (like "90s", "10m", "1h")`
- an `env` name that is not a variable name, or `PATH`, `HOME` or `TMPDIR`: ``command(argv, {
  env }): "<name>" is not a variable the author sets — PATH, HOME and TMPDIR are the Effector's
  (ADR-0067)``
- `pin: true` with no Effect secret in `env`: ``command(argv, { pin: true }) names no secret in
  its env: `pin` chooses the commit a credentialed command runs against in the Sandbox
  Effector, and a command that names no secret runs in the live Worktree (ADR-0067) — name its
  secret, command(argv, { env: { NAME: secret("x") }, pin: true }), or drop `pin` ``

And when it starts, with no Effect secret in `env`:
- outside a `workspace()`: `command "<slot>" runs in the Harness container of a Workspace, and
  its state is not inside a workspace() — invoke it from a Workspace's body (ADR-0067)`
- no `slot` under a Workspace with more than one Repo Slot: `command "<slot>" runs under a
  Workspace carrying more than one Repo Slot (<slots>) and names none — say which Worktree it
  runs in: command(argv, { slot: "<slot>" }) (ADR-0051)`
- a `slot` that the Workspace does not have: `command "<slot>" names Repo Slot "<name>", which
  its Workspace does not carry (<slots>)`
- an `arg()` with no string in `input`: `command "<slot>": argv slot <<name>> has no string
  value in its input (got <type>) — map it in the invoke's input`. The angle brackets around the
  arg's name are in the text: `argv slot <file>`. `<type>` is `nothing` when `input` has no such
  field, or the type of its value, such as `number`.

And when it starts, with an Effect secret in `env`:
- outside a `workspace()`: `command "<slot>" runs in the Sandbox Effector of a Workspace, and its
  state is not inside a workspace() — invoke it from a Workspace's body (ADR-0067)`
- no `slot` under a Workspace with more than one Repo Slot: `command "<slot>" runs under a
  Workspace carrying more than one Repo Slot (<slots>) — say which: { slot: "<slot>" }
  (ADR-0051)`
- a Workspace with no Repo Slot: `command "<slot>" runs under a Workspace with no Repo Slot: a
  command with an Effect secret runs against one commit of a Repo Slot's branch, and there is
  none — declare a Repo Slot, or name no secret in its env, and it runs in /work beside the Agent
  (ADR-0051)`. `jr2 up` refuses it first, with the same reason
- a `slot` that the Workspace does not have: `command "<slot>" names Repo Slot "<name>", which
  its Workspace does not carry (<slots>)`
- `pin: true` and an `input.sha` that is not a full commit id: `command "<slot>" (pin): its
  input's sha is not a full commit id`
- an `arg()` with no string in `input`: the same text as above.

**Parameters**

- `argv`: The program and its arguments: strings and `arg("name")` entries. Or `open`, in a
  Machine that you publish as a package, for the Instance that registers it to bind with
  `customize()`.
- `options` ([`CommandOptions`](type-commandoptions.md)): Optional. `slot`: the Repo Slot whose Worktree the command runs in; needed only
  where the Workspace has more than one. `timeout`: `"90s"`, `"10m"` or `"1h"`, default
  `"10m"`; when it expires, the output is `timeout`. `tail`: how many characters of the end of
  the output stream to keep, default 4096. `env`: environment variables. Each value is
  `secret("name")` for the value of an Effect secret, or `secret("name").path` for the path of a
  file that holds it. `pin`: `true` to run against the commit in `input.sha`. Use `pin` only
  with an Effect secret in `env`.

**Returns**: The actor logic of the Effect's slot. Its output is a `CommandOutput`.

**Example**

```ts
import { z } from "zod";
import { arg, command, fail, jr2Setup, workspace, type Workspaced } from "@jr2/orchestrator";

const runInput = z.object({ branch: z.string() });
type BodyInput = Workspaced<z.infer<typeof runInput>, "app">;

const body = jr2Setup({
  types: {} as { input: BodyInput },
  actors: {
    tests: command(["node", "--test", arg("file")], { timeout: "5m" }),
    fail: fail(),
  },
}).createMachine({
  id: "check",
  on: { "workspace.lost": ".lost" },
  initial: "testing",
  states: {
    testing: {
      invoke: {
        src: "tests",
        input: { file: "test/greet.test.js" },
        onDone: [
          {
            guard: ({ event }) => event.output.outcome === "exited" && event.output.code === 0,
            target: "passed",
          },
          { target: "failed" },
        ],
      },
    },
    passed: { type: "final" },
    // Not final states: each one fails the run, and the run ends `failed`.
    failed: { invoke: { src: "fail", input: { reason: "the test did not pass" } } },
    lost: { invoke: { src: "fail", input: { reason: "the Sandbox is gone" } } },
  },
});

export const machine = workspace(body, {
  input: runInput,
  repos: { app: "https://github.com/you/app.git" },
  spec: ({ input }) => ({ branch: input.branch }),
});
```

**See also**

- [Effects](../../concepts/effects.md)
- [Run a command or call a URL with an Effect](../../guides/run-a-command-or-call-a-url.md)
- [ADR-0067: An Effect is an actor slot, and runs at most once unless marked idempotent](../decisions/0067-an-effect-is-an-actor-slot-and-runs-at-most-once-unless-marked-idempotent.md)
- [ADR-0068: An Effector holds an Effect's secrets and signs its wire](../decisions/0068-an-effector-holds-an-effects-secrets-and-signs-its-wire.md)

## Overload 2

```ts
function command<
  const Opt extends CommandOptions = CommandOptions,
>(
  argv: typeof open,
  options?: Opt,
): EffectLogic<OpenCommandInput<Opt["pin"]>, CommandOutput> & OpenEffect<"argv" | EnvOpenParts<Opt>>
```

Make a `command` Effect whose argv is Open: `command(open, options)`, in a Machine that you
publish as a package. The Instance that registers the Machine binds the argv one time:
`customize(machine, { effects: { <slot>: { argv } } })`. `command` tells the rest, and
what it refuses.

**Parameters**

- `argv`: `open`.
- `options` ([`CommandOptions`](type-commandoptions.md)): Optional. The same options as for a written argv.

**Returns**: The actor logic of the Effect's slot. Its output is a `CommandOutput`.

**See also**

- [Bind a shipped Machine's Open parts](../../guides/bind-open-parts.md)
- [ADR-0054: The kit ships Machines, and a shipped Machine leaves its parts Open](../decisions/0054-the-kit-ships-machines-and-a-shipped-machine-leaves-its-parts-open.md)
