Skip to main content

crewship routine

Manage workspace routines — AI-authored, repeatable workflow recipes that any crew can invoke.
Alias: crewship pipeline (back-compat — every routine X invocation also works as pipeline X) See Routines guide for the conceptual overview.

Subcommands

For batch evaluation across the eval-* routine fleet (matrix sweep, head-to-head tier compare, regression baselines), see the eval CLI.

crewship routine list

List workspace routines, sorted by usage by default.
Output columns: SLUG, INVOC, LAST STATUS, AUTHOR CREW, DESCRIPTION

crewship routine get <slug>

Show full routine detail including the DSL definition.
Output: human header (slug, name, author, invocations) + pretty-printed DSL JSON.

crewship routine save

Save a new routine from a JSON DSL file. Test_run gate runs first; if it fails, save aborts with a structured error pointing at the failing step.
--author-agent is not a credit line. Every crewship step in the routine dispatches under that agent’s identity, and issue.comment has no other source for one — a comment needs an author it can name. Saving a routine that contains an issue.comment step without --author-agent is refused:
The refusal is at save on purpose. The same routine used to save clean and then fail every run with 400: agent_id is required.

crewship routine delete <slug>

Soft-delete a routine after a confirmation prompt. The row is preserved for audit, but the routine is no longer available for normal listing or invocation.
Backed by DELETE /api/v1/workspaces/{ws}/pipelines/{slug}.

crewship routine run <slug>

Invoke a saved routine against the live execution tier.
Output: run_id, status, output, duration_ms, cost_usd. Immediate runs use COMPLETED, FAILED, or CANCELLED; WAITING means the run is parked at an approval waitpoint and is not a failure; DEDUPED means the idempotency key returned the original run. A delayed or debounced invocation returns SCHEDULED with pending_id, fire_at, priority, and coalesced instead of a run id. The API uses 200 for immediate results and 202 for this deferred receipt.
If the routine hits a kind: approval gate, run terminates with status: WAITING (exit 0, not a failure): it prints paused at approval step: <id> plus the waitpoint token and the approve/reject command hints, then releases its execution slot. Resume it with crewship routine waitpoints approve|reject <token>. See HITL waitpoints.With --wait, the CLI instead keeps polling the run until the approval is resolved and the run finishes — the “trigger and get the real outcome” path for scripts and agents. You can also wait later with crewship wait --routine <run_id>. Deferred receipts (SCHEDULED from --delay/--debounce-key) have no run id yet and are not waitable.

Common errors

  • API error (500): pipeline: concurrency_key rendered to empty value (referenced input missing or empty) — the DSL declared a concurrency_key template that references an input you didn’t supply, and the template has no literal text to fall back on. See Troubleshooting in the routines guide for the four fixes.

crewship routine result <run_id>

Prints a finished run’s deliverable: the final output, followed by a Files changed during this run section listing the artefacts on the run’s crew that were written inside the run window (e.g. report.pdf, summary.md) with a fetch hint. For a document-processing routine the files are the deliverable, so this is the one view that surfaces them.
This is a correlation, not proof of authorship. Files are matched by comparing each file’s modification time against the run’s [started_at, ended_at] window under the run’s crew (agent /output + /crew/shared) — no executor changes, so it works for any past run. That means the list can also include:
  • files written by a concurrent chat session on the same crew while the run was in flight;
  • files from another run on the same crew whose window overlapped;
  • files authored earlier but re-saved during the window (an mtime bump with no content change).
Read it as “what changed on the crew while this ran”, not “what this run uniquely produced”.
The --client view lists the same files by bare name only — the internal crew id, agent-slug paths, and the crew files get fetch hint are stripped so the output is safe to forward to a customer. Backed by GET …/pipeline-runs/{runId}/files.

crewship routine report <run_id>

Build a shareable report containing a run’s inputs, step outcomes and outputs, final output, and (in the operator view) cost and duration. Markdown is the default; use -f html for a self-contained HTML page.

crewship routine approve <slug>

Approve a routine sitting in proposed status — the maker-checker checkpoint that flips it live so crews can invoke it. Requires the MANAGER+ role.
Backed by POST /api/v1/workspaces/{ws}/pipelines/{slug}/approve. Output: Routine <slug> → <status>. A 403 role error or 409 “routine is awaiting approval” run refusal is rendered from the server’s Problem Details response.

crewship routine reject <slug>

Reject a proposed routine, removing it. Requires the MANAGER+ role.
Backed by POST /api/v1/workspaces/{ws}/pipelines/{slug}/reject. Output: Routine <slug> → <status>.

crewship routine disable <slug>

Airbag switch: disable an active routine. Cancels any in-flight runs and refuses new invocations (run then returns 409 routine is disabled). Requires the OWNER/ADMIN role.
Backed by POST /api/v1/workspaces/{ws}/pipelines/{slug}/disable. Output: Routine <slug> → <status>; when runs are cancelled a second line reports cancelled N in-flight run(s).

crewship routine enable <slug>

Re-enable a disabled routine so it accepts invocations again. Requires the OWNER/ADMIN role.
Backed by POST /api/v1/workspaces/{ws}/pipelines/{slug}/enable. Output: Routine <slug> → <status>.

crewship routine doctor <slug>

Preflight checklist that walks every checkable precondition before run and emits a ✓/⚠/✗ report. Catches the blind alleys (missing crew provisioning, agent slug typo, missing credential, contradictory validation gate, cost cap too tight) at one round-trip instead of one failure-per-run.
Checks performed:
  • routine_exists — routine present in the workspace
  • author_crew — author crew exists, has a devcontainer config, provisioning status is completed (not in_progress or failed)
  • agent_slugs — every step’s agent_slug and every outcomes.grader_agent_slug resolves to an agent in the author crew (runtime resolution is crew-scoped)
  • credential:<TYPE> — every entry in credentials_required has an active workspace credential of that type
  • egress_allowlistegress_targets declared when http steps are present; no dead entries that match no host (*, *.*, empty); warns on loopback hosts (localhost, 127.0.0.0/8) — legitimate on dev boxes, so a warning, not a failure
  • cost_capmax_cost_usd is set and is at least 1.5× estimated_cost_usd
  • validation:<step_id> — gates are structurally satisfiable (min_length ≤ max_length, no string in both must_contain and must_not_contain)
Exit code is non-zero if any check FAILed. WARN does not propagate to the exit code — it’s an operator hint, not a blocker.

crewship routine bench <slug>

Run a routine N times with the same inputs and report pass-rate, cost stats, and latency distribution. Statistical stability characterisation — use to decide whether a routine is production-ready at its current tier.
Output reports total / mean / p95 / max cost; p50 / p95 / max duration; fail-reason breakdown bucketed into cost-cap / rubric-fail / gate-fail / auth-fail / timeout / other. Headline verdict label so the operator doesn’t need to interpret the stats:

crewship routine backtest <slug>

Replay a corpus of recent, successful runs against a candidate version and diff the outputs — validate a routine change before rollout, without creating a new version or touching which version is live. Read-only: --against names an immutable versions row; the backtest never bumps head_version and never changes what run/schedules/webhooks execute.
Composes two existing primitives — no new engine surface:
  1. Corpus selectionGET /api/v1/workspaces/{ws}/pipelines/{slug}/run-records?status=completed, filtered client-side to the --last window (newest-first, capped at --limit).
  2. Pinned-version replay — for each corpus run, POST /api/v1/workspaces/{ws}/pipelines/runs/{run_id}/replay with {"pinned_version": N}. This replays that run’s original captured inputs against the pinned version’s definition instead of head (same PinnedVersion mechanism the cron scheduler and webhook dispatch already use for pinned triggers) and is graded against what was actually recorded.
Per-run verdict: Aggregate verdict (also the process exit code — REGRESSION_DETECTED exits non-zero, CI-friendly like eval baseline diff): For a matrix sweep across many routines/tiers or a saved regression baseline, see eval scenarios and eval baselinebacktest answers a narrower question (“is this candidate version safe to promote?”) using the workspace’s own recent traffic as the corpus instead of an authored eval suite.

crewship routine appearance

A routine’s icon and colour — how it renders in lists. A workspace ends up with dozens of routines that look identical; an icon makes one findable at a glance, using the same set crews and projects use. Presentation only. It never touches the definition, so setting an icon does not create a routine version, does not re-run the governance risk check, and does not invalidate a save token. Leave a routine unset and the UI derives a stable icon from its slug, so every routine still looks different without anyone choosing.

routine appearance get <slug>

routine appearance set <slug>

Only the flags you pass are sent: an absent field is left alone, so changing a colour never wipes an icon you did not mention. --clear removes both. --color takes a palette id: blue, emerald, violet, amber, rose, cyan, lime, fuchsia. API: PATCH /pipelines/{slug}/appearance.

crewship routine budget

Per-routine monthly spend budget (#1422 item 3) — a cap you set out-of-band from the DSL, compared against actual pipeline_runs.cost_usd for the current calendar month. Distinct from DSL max_cost_usd (a per-RUN hard gate the executor enforces mid-run); this is a budget-vs-actual view, not an enforcement gate — going over doesn’t block the next run.

routine budget get <slug>

Requires crewship login and a workspace context; the API is a member-scoped read. --format json returns the response fields exactly, including month, spent_usd, and (when a positive cap exists) pct_used and over_budget.
Prints an ASCII meter + $spent of $cap (pct%), or a “no budget set” hint with the current spend if none is configured. --format json returns {slug, has_budget, monthly_budget_usd, month, spent_usd, pct_used, over_budget} (pct_used/over_budget are omitted when has_budget is false).

routine budget set <slug> --amount <N>

Setting a budget never touches the DSL and never bumps the routine’s version history — it’s independent of routine save/rollback. The mutation requires the OWNER/ADMIN manage capability and returns 200 OK with the same budget object as budget get.

routine budget summary

Workspace-wide roll-up: every routine with a budget set or spend this month (a routine with neither is excluded — a fresh workspace can have dozens of zero-cost deterministic routines, and listing all of them would be noise). Table columns: ROUTINE BUDGET SPENT PCT STATUS (STATUS shows OVER when spend exceeds the cap), plus a workspace total line. --format json returns {month, routines: [...], total_budget_usd, total_spent_usd}. This is a member-scoped read and returns 200 OK; each routine row includes slug, monthly_budget_usd, spent_usd, and optional pct_used/ over_budget.

crewship routine state

Inspect and repair a routine’s durable cross-run state — the key/value bucket behind {{ routine.state.<key> }} and a step’s state_write binding. See Cross-run state for how a routine reads and writes it. State is scoped per (routine, schedule): two cron schedules of the same routine keep independent watermarks, and manual / webhook runs share a separate bucket shown as (manual/webhook). That isolation is the reason this command exists — when a routine “stops seeing new items”, the stuck cursor sits in a bucket you can’t guess from the outside, and until now nothing outside a run could read it.
Mutations are ADMIN-tier. A watermark governs what every future unattended run does — rewriting one has the same blast radius as disabling the routine.

routine state list <slug>

Prints each bucket as schedule: <id> followed by a KEY VALUE UPDATED table. Omitting --schedule shows every bucket — that’s the diagnostic default. UPDATED is usually the tell: a cursor frozen days ago next to schedules that ran this morning. Passing --schedule "" explicitly is not the same as omitting it: an empty value selects the shared manual/webhook bucket, while an absent flag means “all buckets”. --format json returns {slug, buckets: [{schedule_id, entries: [{key, value, updated_at}]}]}.

routine state set <slug> <key> <value>

Writes one key into one bucket. The next run of that schedule reads the new value from {{ routine.state.<key> }}. This is the preferred repair — rewinding to a known-good cursor is bounded, where clearing is not.

routine state rm <slug> <key>

Removes one key. The next run reads it as empty. A key that was never written returns 404 rather than a silent success — a mistyped key is the usual reason a delete “doesn’t work”, and reporting success would send you looking in the wrong place.

routine state clear <slug>

Drops every key in one bucket and reports how many went. There is deliberately no “clear every schedule” form: each schedule’s cursor is an independent watermark, and dropping all of them at once makes every schedule reprocess its whole backlog with no undo.

crewship routine records <slug>

List runs for a routine using the pipeline_runs projection (introduced in migration v83). Backed by indexed columns, so this is O(log n) where runs is O(scan-with-LIKE) over journal_entries. Prefer records for filterable / scriptable run history.
Falls back with a clear message to crewship routine runs <slug> when the server predates v83.

Reading the TRIGGER and CHAIN columns

TRIGGER is not a verbatim print of the row’s triggered_via. Every deferred run — cron schedules and automations alike — is fired by the same dispatcher and stored with triggered_via: "schedule", so the raw field cannot tell the two apart. When a run carries an automation_name, the column reads automation and SOURCE names the rule that started it:
CHAIN appears only when some run on the page was composed rather than started directly — a routine that another routine called, or one an automation fired from an event a run emitted. The number is chain_depth: hops from whatever a human did, capped at 8. A workspace where nothing composes never sees the column. Structured output (--format json|yaml|ndjson) always carries the underlying fields, whether or not the table drew them:

crewship routine runs <slug>

List recent invocations from the routine’s journal history. The human table shows timestamp, event type, severity, run ID, and summary; structured formats return the complete rows.
Backed by GET /api/v1/workspaces/{ws}/pipelines/{slug}/runs.

crewship routine active

List in-flight runs across the entire workspace (not scoped to a single slug). Useful when triaging “is anything still running?” before a deploy or restart.
Backed by GET /api/v1/workspaces/{ws}/pipelines/runs/active — the handler note in internal/api/pipeline_runs.go flags it as single-replica scope: in a multi-replica deployment the response only reflects runs hosted by the replica the request landed on. Output columns: RUN_ID (truncated), SLUG, STARTED, CANCEL_REQ, CONCURRENCY_KEY. Empty list prints No active runs. Pair with routine cancel <run_id> to terminate any of the listed runs.

crewship routine logs <run_id>

Fetch the full journal trace for a single run. Post-mortem and CI-diagnostics surface — every step’s prompt, output, validation verdict, and cost in one document.
--show-outputs prints the full, untruncated output each step returned (from the run’s persisted step_outputs), for post-mortem “what did this step actually return?” debugging — the slug-free state view previously showed only status/error, and routine run truncates step outputs to a preview. Works on the slug-free form (routine logs <run_id> --show-outputs); the whole run map (including step_outputs) is also available via --format json.
Example output (table mode, with --slug):
The DURATION and COST columns surface the per-step duration_ms / cost_usd from the journal payload (em-dash means “not applicable” — started / failed events don’t carry them). Matches the Runs-tab waterfall in the UI so the same numbers reconcile across surfaces. Pair with routine watch for live observation and routine logs for post-hoc forensics.

crewship routine dry-run <slug>

Preview what the routine WOULD execute without invoking agents. Returns a structured would_execute report with each step’s resolved tier, rendered prompt, and estimated cost.
Example output:
Zero side effects — useful for “is this routine wired correctly?” without burning tokens. The cost figure is an order-of-magnitude estimate (flat token-density heuristic, not real pricing). The same report renders inline in the UI: click Dry run in the routine detail panel and the report appears above the tab bar.

crewship routine validate [file.json|file.yaml]

Offline DSL validation with no server call (by default). Reads from the file argument or stdin if absent.
Exit code 0 = valid, 1 = invalid. CI-friendly:
Runs the same parse + validate + step-id-uniqueness checks the server would, except cross-routine cycle detection (which needs the workspace’s full call graph) and slug uniqueness (which needs the DB). Beyond the schema checks, validate catches three authoring mistakes that used to only surface at save or the first run:
  • concurrency_key that can render empty — a key built entirely from {{ inputs.X }} refs where none is required: true/defaulted (e.g. "{{ inputs.account_id }}" with account_id optional) is rejected: a run that omits the inputs renders the key empty and dies at run time (concurrency_key rendered to empty value). Anchor one referenced input, or add literal text ("tenant-{{ inputs.account_id }}" always renders non-empty) — both accepted.
  • Unsatisfiable output gates — a step whose validation has min_length > max_length, or the same token in both must_contain and must_not_contain, is rejected: no output could ever pass it.
  • Dead egress entry — an egress_targets value of *, *.*, or an empty host matches no real host at run time (targets are literal/subdomain-suffix matched, not globbed), so it silently denies every http step’s egress. Rejected at author time. List real hostnames instead, or omit egress_targets entirely for unrestricted egress. (Loopback hosts like localhost/127.* are matchable and stay a doctor warning, not a hard error — legitimate on dev boxes.)

Resolving agent slugs offline

By default validate skips the “does this agent_slug exist?” check because it needs the crew roster. Supply the roster to make typos fail here instead of at save:
A typo’d agent_slug (or an outcomes.grader_agent_slug) not in the resolved set fails validation, naming the step — and, if a close match exists in the resolved roster, suggests it (did you mean: triage?). The same fuzzy suggestion applies to an unresolved {{ inputs.X }} reference against a typo’d input name.

Every failure in one pass, with a jump-to path

validate no longer stops at the first problem — it accumulates every static-check failure it finds and reports them all together, each prefixed with a JSON-pointer path to the offending field (/name, /steps/2/agent_slug, /steps/0/http/url, …) so an editor/LSP integration can jump straight there:
This matters most for the capabilities | claude -p → validate → fix authoring loop (see Routines cookbook): an AI author fixing every reported field in one revision converges in fewer round-trips than fixing one error, re-running, hitting the next one, and repeating. The one exception is the step-count bound (MaxPipelineSteps, 500) — a definition already over that limit fails immediately on its own, since walking the rest of the checks against an oversized definition isn’t meaningful.

crewship routine capabilities <crew>

Dump the authoring bundle for a crew: the routine DSL schema, agent slugs, container CLIs and datastores, connected integrations and enabled tools, and wired runtimes. The human view is a summary; JSON/YAML/NDJSON include the full bundle, including the schema.
Backed by GET /api/v1/crews/{crewId}/capabilities.

crewship routine init

Scaffold an editable routine DSL locally. With no flags it prints a minimal one-step agent_run skeleton; --script emits a deterministic script-backed scaffold; --from clones an existing routine definition and requires the server. --from and --script are mutually exclusive.

crewship routine schema

Print the published draft 2020-12 routine DSL JSON Schema, or write it to a file for editor autocomplete and offline authoring.

crewship routine step-run <slug> <step>

Execute one agent_run, http, script, or transform step in isolation, without a DAG or persisted run record. wait, notify, call_pipeline, and code steps are unsupported. HTTP and script steps still perform their real egress/container work.
The human output includes the PASS/FAIL validation verdict and output; JSON, YAML, and NDJSON return the structured result. Backed by POST /api/v1/workspaces/{ws}/pipelines/{slug}/step_run.

crewship routine tag <slug>

Add or remove discovery tags on a routine definition — distinct from run tags. Definition tags power cross-crew discovery via routine list --tag <tag>. Pass --add (repeatable) and/or --remove; at least one is required.
Adds are sent as PUT /api/v1/workspaces/{ws}/pipelines/{slug}/tags; a removal is DELETE /api/v1/workspaces/{ws}/pipelines/{slug}/tags/{tag}. When both flags are passed, the add runs first (Tagged … +[…]), then the removal (Untagged … -…).

crewship routine step-override

Override a single step’s prompt or model without bumping the routine version — the change applies on the next run and can be cleared to revert to the authored value. A parent command with three children.

step-override set <slug> <step_id>

Set a prompt and/or model override for one step. Pass at least one of --prompt / --model.
Backed by PUT /api/v1/workspaces/{ws}/pipelines/{slug}/steps/{step_id}/override.

step-override clear <slug> <step_id>

Remove a step’s override, reverting it to the authored prompt/model.
Backed by DELETE /api/v1/workspaces/{ws}/pipelines/{slug}/steps/{step_id}/override.

step-override list <slug>

List the active step overrides for a routine.
Backed by GET /api/v1/workspaces/{ws}/pipelines/{slug}/overrides. Output columns: STEP, MODEL, PROMPT (truncated to 50 chars). No overrides prints No step overrides — routine runs as authored.

crewship routine watch <slug>

Stream live run + step events for a routine. Polls every 2s, dedupes events, prints in chronological order with ANSI colour by status.
Ctrl-C / SIGTERM exit cleanly.

crewship routine versions <slug>

Output columns: VERSION, HEAD, PARENT, HASH, AUTHOR, CREATED, SUMMARY. The * marker tags the current HEAD.

crewship routine versions show <slug> --version N

Show one immutable version’s metadata and full DSL definition.
Backed by GET /api/v1/workspaces/{ws}/pipelines/{slug}/versions/{version}.

crewship routine diff <slug> --from N --to M

Unified diff between two versions’ definitions (#1422 item 5) — versions show <n> dumps one version at a time for external diffing; this is the native in-product equivalent. The diff is computed over pretty-printed JSON, so a single field change reads as a single-hunk diff instead of the whole minified blob changing.
Identical versions (same definition_hash) print a one-line confirmation instead of an empty diff. --format json returns {slug, from_version, to_version, from_hash, to_hash, identical, unified_diff}. crewship routine rollback <slug> --to N calls this same endpoint after a successful rollback to print a “What changed” view against the version it just rolled back from — best-effort, so a diff-fetch failure never fails the rollback itself.

crewship routine iterate <slug>

Run a scored improvement loop over a saved routine: run → a grader agent scores the output against your rubric ({"score": 0-100, "feedback": "..."}) → below target, an optimizer agent rewrites the definition → local validation → you confirm → the definition saves as a new immutable version whose change_summary records the round and score (iterate round 2: score 74/100 — misses the TL;DR). The loop stops at --target or after --rounds.
Guardrails, by construction:
  • Every save goes through the normal gates — the server-side test-run gate and governance maker-checker apply exactly as for a human author. A definition that gains risky steps (http, code, egress) lands as proposed and the loop stops with a message; the optimizer prompt also instructs against adding such steps.
  • Everything is versioned — each accepted round is one row in routine versions; crewship routine rollback <slug> --to N undoes any of it.
  • Human-in-the-loop by default — each save asks for confirmation; --yes is for unattended runs and stays auditable via the version trail.
  • A run parked on an approval waitpoint aborts the loop (iterate does not drive approval flows); deferred/deduped runs abort too.
Exit summary lists per-round status, score, and whether a version was saved; --format json emits the same machine-readable.

crewship routine rollback <slug>

Creates a new version on top of HEAD whose definition equals v3’s. History is preserved; you can roll forward by another rollback.

crewship routine export <slug>

Output: crewship-pipeline-bundle/v1 JSON to stdout. By default any type: script files the routine references are inlined into the bundle so it is portable. Suitable for pipe to routine import or commit to git.

crewship routine import [bundle.json]

Pre-validates JSON locally before sending — typos at the keyboard get a local error rather than a 400 from the server.

crewship routine cancel <run_id>

Signals the run goroutine. Run terminates at the next safe point and emits pipeline.run.failed with reason “cancelled”. Already-terminal runs return 409.

crewship routine tree <run_id>

Show a run together with its child runs — those spawned via call_pipeline steps, deferred dispatch, or replay. Useful for tracing a fan-out or a parent→child chain end-to-end.
Backed by GET /api/v1/workspaces/{ws}/pipeline-runs/{run_id}/tree. Output columns: RUN ID, PARENT ((root) for the top run), ROUTINE, STATUS, VIA (triggered_via), COST.

crewship routine replay <run_id>

Re-run a prior run with its original captured inputs. The new run is stamped is_replay=true + replay_of=<run_id> (steps can short-circuit side effects via {{ env.is_replay }}) and inherits the source run’s tags so it groups with the original.
Backed by POST /api/v1/workspaces/{ws}/pipelines/runs/{run_id}/replay (body: {"pinned_version": N} when --version is set). Output: Replayed <run_id> → new run <new_id>: <status> ($cost), plus the run output if non-empty.

crewship routine metadata <run_id>

Mutate a run’s metadata scratchpad. Later steps read the result as {{ run.metadata.x }}. Each flag takes a JSON object; pass at least one.
Backed by PATCH /api/v1/workspaces/{ws}/pipeline-runs/{run_id}/metadata. Prints the updated metadata object.

crewship routine signal <run_id>

Deliver a payload to a running run parked on a wait:event step (input-stream injection). The payload becomes that wait step’s output.
Backed by POST /api/v1/workspaces/{ws}/pipeline-runs/{run_id}/signal. Needs a run id, so it only works when you already have one. To wake every run parked on an event without knowing which they are, use crewship signal send instead.

crewship routine errors

List the workspace’s failed runs bucketed by a stable error fingerprint (failing step + normalized message) so like failures group together. Pick a fingerprint and replay the whole bucket with bulk-replay.
Backed by GET /api/v1/workspaces/{ws}/pipelines/runs/errors. Output columns: FINGERPRINT, COUNT, ROUTINE, STEP, SAMPLE ERROR (truncated to 60 chars). No failures prints No failed runs. 🎉.

crewship routine bulk-replay

Replay every failed run under one fingerprint — the fix-and-retry companion to errors. Ship the fix, grab the fingerprint, then re-trigger the whole bucket.
Backed by POST /api/v1/workspaces/{ws}/pipelines/runs/bulk_replay. Output: Bulk replay: <replayed>/<requested> runs re-triggered for fingerprint <fp>.

crewship routine pending

List or cancel deferred routine triggers — the parked dispatches created by run --delay, --ttl, --debounce-key, or --priority. A parent command with two children.

pending list

Requires login and workspace context. Backed by GET /api/v1/workspaces/{ws}/pipelines/pending (200 OK, member-scoped), which returns at most 100 not-yet-fired rows with id, pipeline_slug, optional debounce_key, priority, and RFC3339Nano fire_at. Output columns: PENDING ID, ROUTINE, PRIORITY, DEBOUNCE KEY, FIRES AT. Nothing pending prints No deferred triggers pending.

pending cancel <pending_id>

Cancels a trigger before it fires. Requires the update capability (MANAGER+). Backed by POST /api/v1/workspaces/{ws}/pipelines/pending/{pending_id}/cancel, which returns 200 OK with { "ok": true, "cancelled": "<pending_id>" }. Missing, expired, already-fired, or already-cancelled items return 404; missing database wiring returns 503.

crewship routine schedules

Manage cron-driven triggers. Scheduler runs in-process and ticks every 30s.

schedules list

Output columns: ID, NAME, ROUTINE, CRON, TZ, ENABLED, FAILS, WAKE, NEXT, MISSED A schedule pinned to a specific routine version shows the pin in the ROUTINE column as slug@vN (e.g. daily-digest@v3). The --json output carries it as target_pipeline_version, plus last_status (COMPLETED | FAILED | SKIPPED | WAITINGWAITING means the run parked on an approval gate, which is healthy, not a failure).

schedules create

create is the only command that mints a schedule ID, and every other schedule command takes that ID with no prefix-matching fallback — so to script the family, read it back with a machine format rather than parsing the human output:

schedules update <id>

Pass any subset of --cron / --timezone / --name / --enabled / --inputs / --catchup / --pin-version / --unpin / --max-failures / --wake-slug / --wake-inputs / --no-wake / --fail-closed to update. --max-failures is the positive consecutive-failure threshold at which the schedule auto-disables; omitting it keeps the existing threshold. An existing version pin survives updates that don’t mention it; --pin-version N re-pins, --unpin removes the pin so fires track head again. Likewise the wake gate is preserved unless touched: --wake-slug sets/replaces the probe, --wake-inputs replaces its inputs, and --no-wake removes the gate so the schedule fires on every tick again (--no-wake and --wake-slug/--wake-inputs/--fail-closed are mutually exclusive). The fail-closed policy is preserved unless mentioned: --fail-closed holds the run on a probe failure, --fail-closed=false restores fail-open. --catchup sets skip, once, or all; omitting it preserves the current policy.

schedules enable <id> / schedules disable <id>

Toggle without altering the cron expression.

crewship routine schedules enable <schedule_id>

Enable a schedule. The next scheduler tick can fire it; the cron expression and other schedule settings are unchanged.
The command takes exactly one schedule ID and has no command-specific flags. Human output is Schedule <schedule-id> enabled. With --format json, the acknowledgement contains the schedule id and enabled: true.

schedules now <id>

Force-fire ad-hoc (out-of-cycle invocation for testing). Honours the schedule’s version pin — it executes exactly what the next cron tick would, and answers 409 if the pinned version no longer exists.

schedules delete <id>

--yes skips the confirmation prompt.

Machine-readable output

Every schedules subcommand honours the global -f/--format (table|json|yaml|ndjson). list and create emit the full API row; now, update, enable, disable, and delete emit an acknowledgement carrying the schedule id plus the verb that was applied:
The default (table) output stays human-readable and is unchanged.

crewship routine webhooks

Manage event-driven triggers. Each webhook is token-addressed (POST to /api/v1/webhooks/{token}), optionally HMAC-signed for delivery integrity, and rate-limited per token.

webhooks list

Output columns: ID, NAME, ROUTINE, HMAC, FIRES, LAST STATUS, RATE/MIN, ENABLED The explicit --json flag is a deprecated alias for --format json. A version-pinned webhook shows the pin in the ROUTINE column as slug@vN. LAST STATUS is COMPLETED | FAILED | DEDUPED | WAITING (WAITING = the dispatched run parked on an approval gate — healthy, resumes on approval).

webhooks create

Stripe-style secret reveal: the signing secret is shown ONCE in the response. Save it now or rotate via delete+recreate.

webhooks url <id>

Print the public URL without re-revealing the signing secret. Fails if the token is no longer retrievable — see below.

crewship routine webhooks url <webhook_id>

Print the public URL for an existing webhook without revealing its signing secret. The command lists webhooks in the active workspace and matches the exact ID.
The default base URL comes from the configured server (or CREWSHIP_SERVER); the printed value is <base-url>/api/v1/webhooks/<escaped-token>. If the ID is not found, the command exits with webhook <id> not found.
Since tokens are hashed at rest (#1888), this command only works for webhooks whose token the server still returns. The token is shown once, in the create response — the list endpoint this command reads returns an empty one. When that is the case the command fails and exits non-zero, naming the delete + create pair that mints a new token, rather than printing …/api/v1/webhooks/ with nothing after the slash. Copy the URL at create time; a lost token cannot be recovered, only replaced.

webhooks delete <id>

Existing senders using the deleted token will start getting 404s. --yes skips the confirmation prompt.

crewship routine waitpoints

Inspect + decide on pending HITL approval waitpoints.

waitpoints list

Output columns: TOKEN, RUN ID, STEP, KIND, CREATED, TIMEOUT, PROMPT

waitpoints show <token>

Full prompt + metadata for a single waitpoint.

waitpoints approve <token> / waitpoints reject <token>

The decision comment is forwarded to the parked run as the wait step’s output, so downstream steps can read approval rationale via {{ steps.<wait_step_id>.output }}.

crewship routine trust

Manage standing approval grants — the answer to “I have approved this same gate twenty times”. A grant is scoped to one gate of one routine and pinned to that routine’s current definition hash. Editing the routine moves the hash, so no grant matches and the gate asks again. A grant never becomes a silent path: when it fires it writes an ordinary approved waitpoint attributed to the operator who granted it. MANAGER+ is required to grant or revoke. Listing is readable by anyone who can see the routine.

trust list <slug>

Output columns: GRANT ID, STEP, STATE, USES, GRANTED, DEFINITION, REASON STATE is live (still firing), revoked (somebody withdrew it), or spent (expired or out of uses). A DEFINITION marked (stale) belongs to a routine body that has since changed — the grant is already inert. Revoked grants stay in the list on purpose: the audit question is who trusted this gate and who took it back, which a live-only list cannot answer.

trust grant <slug> --step <step_id>

A second grant for the same gate and definition returns 409 rather than resetting the existing grant’s use counter.
When the governing crew is at autonomy_level: strict, standing grants are ignored entirely — strict means every governable action reaches an operator, regardless of what has been granted. The governing crew is the routine’s author crew (a routine runs in its author’s context), or the invoking crew on a cross-crew invocation.

trust revoke <slug> <grant-id>

The gate blocks again on the next run. The grant row is kept and marked, not deleted.

The audit trail

Granting and revoking each write a journal entry, because disarming a human gate is the bluntest decision the approval system offers and it used to be the only one that left no trace: Both are attributed to the deciding user (actor_type: user). definition_hash is the load-bearing field — a grant only fires against that exact routine body, so an entry without it records that something was trusted but not what.
One-off decisions and standing ones share the approval. namespace because they are the same control at two levels, so listing both families together answers “who let this through, by any route”:
Name every type. --type is matched exactly — there is no prefix or glob syntax — so --type approval.* is not a shorthand for the line above. The CLI rejects a wildcard rather than forwarding it, because a filter that matches nothing exits 0 with an empty page, and an empty page on this question reads as “nobody ever disarmed a gate”.
A revoke that changed no row emits nothing — an entry for a withdrawal that did not happen would make the journal disagree with the table. Neither entry sets crew_id, agent_id, mission_id or trace_id, so crewship journal --crew/--agent/--mission/--trace-id cannot narrow to them. --since and --actor-type user can. The refs (trust_grant_id and friends) are not searchable: -q searches summary and payload only, and refs are in neither — but the routine slug and step id are repeated into the summary, so -q <slug> does find a gate’s decisions. crewship journal get <entry-id> is what prints refs and payload; the list view shows neither.

Global flags

All routine subcommands accept these global flags:

See also