Skip to content

kici: runs & approvals

Execute workflows locally or remotely. A bare kici run [event] performs a real routed run with this machine as the ephemeral agent; the remote subcommand runs fixtures through an orchestrator.

Run a workflow on this machine as a real routed dispatch. kici run <event> --local compiles your workflows, matches triggers against the specified event, expands matrices, and executes the matched jobs — this machine joins as an ephemeral agent through the warm local dev plane. No orchestrator deployment is required.

kici run local is retired, because every run is a real routed dispatch. Invoking kici run local <event> prints a hint pointing at kici run <event> --local and exits without running.

Terminal window
kici run [event] --local [options]

Concurrency enforcement:

A local run is a real routed dispatch, so a workflow’s concurrency block is enforced by the local dev plane’s own orchestrator — the same machinery a deployed orchestrator uses:

  • The group callback is evaluated agent-side against the simulated event (the same { branch, event } context the agent sees), and the resulting key is reported back to the plane’s orchestrator before steps execute.
  • cancelInProgress: true supersedes the older run in the group; false queues the newer run behind it.

Coordination is scoped to that plane, whose state lives under ~/.kici/local/ — so enforcement is per-machine and per-user, and running the same workflow on two different machines does not serialize across them. That requires a deployed orchestrator.

Execution isolation:

By default, kici run <event> --local executes steps inside an isolated tmp checkout rather than against your real working directory. Any file a step writes, builds, or deletes — and any git mutation a step performs — lands in that throwaway copy, so casual local runs never touch your tree.

What gets materialized into the isolated checkout has full parity with what kici run remote reconstructs: your current working tree minus gitignored files, with .kiciignore applied to local changes, over a real .git directory. Concretely, the checkout is built from a clone pinned to your current HEAD, with your local overlay (modified, staged, and untracked-but-not-ignored files) copied on top and locally-deleted files removed. Workflows that read git metadata work because the .git directory is present and pinned to your HEAD.

The checkout is a fresh temp directory named kici-local-run-<random> under the system temp directory (for example, /tmp/kici-local-run-ab12cd).

Cleanup policy:

  • The isolated checkout is removed when the run finishes, whether it succeeded or failed.
  • A hard process death (SIGKILL, OOM kill) skips that cleanup. Leftovers older than a day are swept by the agent’s own startup garbage collection, which every kici run <event> --local invocation triggers.

Set the KICI_TMPDIR environment variable to place the isolated checkout (and every other KiCI-created temp directory) under a base directory other than the system temp directory.

Secrets are always sourced from your real .kici/ directory, not from the isolated checkout. Gitignored secret files (such as .kici/.env.local and .kici/secrets.yaml) are never copied into the checkout, so a step that reads a secret still gets it from the original location.

Pass --in-place to run against the real working directory instead — useful when you explicitly want in-tree execution. Both modes need a git repository, because the run triggers on a commit. Outside a git work tree, the command fails and tells you to initialize a repository (git init).

The run’s event carries the branch you have checked out, in both modes, so a push({ branches }) filter matches as it would for a push to that branch. On a detached HEAD, the branch is kici-local (a kici run remote fixture uses main).

The run’s changed files are the commit it runs. In the default isolated mode, that commit holds your uncommitted changes, so paths filters and ctx.changedFiles rules see those files. With no uncommitted changes, or with --in-place, the run uses your HEAD commit, so they see the files HEAD changed.

If the trigger times out:

The command waits up to 60 seconds for the local dev plane to create the run. If nothing appears in that window it stops and names the cause it read back from the plane:

MessageWhat it means
plane is not leader yet (election grace period)The plane had not finished electing itself. A single-machine plane elects within seconds; if it does not, read the plane log (kici local logs).
no kici.lock.json at <commit> in <path>The plane resolved no lock file for the commit the run packed, so nothing matched. Commit .kici/kici.lock.json, or make sure it is present in the working tree.
the plane recorded delivery <id> as "<status>"The plane processed the trigger and wrote down a terminal status for it without creating a run. The status names the stage that stopped.
no run appeared for delivery <id> — see <log>Neither the plane’s cluster state nor its delivery record explained the timeout. The named log is the next place to look.
no run appeared for this trigger — the plane never accepted the webhookEvery webhook POST came back without a delivery id, so the plane never queued the trigger. Check the plane is up (kici local status), then read the named log.

Examples:

Terminal window
# Run workflows matching a push event on this machine
kici run push --local
# Run a pull-request-open workflow locally
kici run pr:open --local
# Reuse the working tree instead of an isolated clone
kici run push --local --in-place
# Force the throwaway/offline plane
kici run push --local --offline
# Environment variable overrides
kici run push --local --env NODE_ENV=test --env CI=true
# Quiet mode (summary only, no streaming)
kici run push --local --quiet

An event that matches several workflows creates one run per workflow. kici run --local prints a Run started: line for each run, follows all of them, and names each job <workflow> / <job> in the summary table.

Exit codes:

CodeMeaning
0All workflows succeeded
1One or more jobs failed

Execute fixtures remotely through the full CI pipeline. Fixtures are defined in .kici/tests/*.ts using the fixture() factory function. Without arguments, lists available fixtures.

Remote runs route through the Platform. Authenticate with a personal access token (kici login), then target an organization with kici org use <org> or the --org flag. The Platform relays the run to the org’s orchestrator, while your working-tree overlay uploads directly to object storage — see How the run is routed and The two planes below.

Like kici run <event> --local, kici run remote recompiles your workflows (.kici/workflows → kici.lock.json) before dispatching, so the orchestrator matches and dispatches against your current workflow definitions — a brand-new or edited workflow takes effect without a separate kici compile. A compile or validation error aborts the run before anything is uploaded.

The orchestrator must have cache storage configured (KICI_STORAGE_TYPE = s3 or filesystem) with a dev-reachable upload endpoint so the CLI’s direct upload succeeds; see the testing guide and Storage layout for setup.

Terminal window
kici run remote [fixture] [options]

--approve-all works in --json / --quiet mode: the run still auto-approves each gate it holds on, and the auto-approve diagnostics are written to stderr so stdout stays a pure JSON (or summary-only) payload. Without --approve-all, a --json / --quiet run that hits a gate stays held and prints a one-line “run held; approve via the dashboard or kici approve <run-id>” notice to stderr per hold.

Examples:

Terminal window
# List available fixtures
kici run remote
# Run a single fixture against the active org
kici run remote push-main
# Target a specific org for this run
kici run remote push-main --org xyz789ghi012
# Target a specific orchestrator cluster within the org
kici run remote push-main --orchestrator us-east
# Run all push-related fixtures
kici run remote 'push-*'
# Run everything
kici run remote --all
# Run a specific workflow directly (bypass trigger matching)
kici run remote --workflow ci
# Send a fixture's event to one workflow only (bypass trigger matching)
kici run remote push-main --workflow ci
# Quiet mode -- just pass/fail
kici run remote push-main --quiet
# JSON output for scripting
kici run remote push-main --json
# Fire and forget
kici run remote push-main --no-wait
# View recent test run history
kici run remote --history
# Interactively pick which fixtures to run (multi-select)
kici run remote --pick
# Narrow runsOnAll jobs to a subset of the host roster
kici run remote deploy --target role:web
# AND-combine repeated --target values (hosts must match every selector)
kici run remote deploy --target role:web --target dc:eu
# Skip a runsOnAll job instead of failing it when the target matches no host
kici run remote deploy --target role:gpu --target-allow-empty

Interactive fixture selection (--pick / -p):

Pass --pick (or -p) to open an interactive checkbox menu of the available fixtures. Toggle one or more with space, confirm with enter, and the selected fixtures run through the normal remote pipeline (honoring --parallel, --no-wait, and the other run flags). Notes:

  • --pick is mutually exclusive with a fixture argument, --all, and --workflow. Passing any together exits with code 2.
  • When stdin is not a TTY, --pick prints the available fixtures and exits without running anything — pass a fixture name (or --all) in scripts.

Running one workflow (--workflow):

--workflow <name> runs only the named workflow and bypasses trigger matching.

  • Without a fixture, the workflow gets a manual event.
  • With a fixture (or a glob, or --all), each selected fixture’s event goes to that workflow only. The other workflows the event matches do not run.
  • --workflow wins over a workflow the fixture names itself. The CLI prints a warning when the two differ.
  • A name that is not in the compiled lock file stops the run before anything is uploaded. The error lists the workflows the lock file contains.
  • --workflow cannot be combined with --pick.

Without --workflow, a fixture runs every workflow whose triggers match its event. Each matched workflow gets its own run, as it does for a webhook, so two workflows that define a job of the same name never share a run:

  • The CLI prints one Run started: <run-id> (<workflow>) line per run, follows every run at once, and prefixes each streamed log line with its workflow.
  • The summary table, the error blocks and the --junit report name each job <workflow> / <job>. Each failed job gets its own error block, which holds only that job’s output.
  • Ctrl-C cancels every run. --no-wait prints every run id and returns.
  • kici run remote --history records each run on its own line.
  • --json keeps one entry per fixture, so its summary counts fixtures. The entry’s status is the combined result. runs lists each run (runId, workflowName, status, jobs), and each job carries workflowName. The entry’s top-level runId names the first run and is deprecated: read runs instead.

A kici older than 0.21.0 follows a single run. For it, the orchestrator runs the matched workflows as one shared run when their job names cannot collide. When they can collide, it refuses the fixture. That happens when two workflows define a job of the same name, or when one defines dynamic jobs. The refusal names the shared job names and asks you to upgrade kici or pass --workflow.

--target <selector> is a runtime narrowing for runsOnAll jobs, analogous to Ansible’s --limit. A runsOnAll job normally fans out to every roster host matching its predicate, one pinned execution per host. --target intersects that matched roster with a label selector, so the effective host set is runsOnAll ∩ target:

  • Narrow-only. --target can only remove hosts from the matched set, never add them. The widening dimension (OR across host groups) lives in the workflow’s runsOnAll; --target only subtracts.
  • Run-global, runsOnAll-only. A single --target applies to every runsOnAll job in the run. Jobs pinned to a single host with runsOn are untouched.
  • Repeatable and AND-combined. Each --target value is its own selector; a host must satisfy all of them to survive the narrowing. Use a single value for an OR-style match within one selector and repeated values for AND.
  • Selector syntax matches runsOn: an exact label (role:web), a glob (role:*), or a regex (/^box-0[1-3]$/).

When --target narrows a runsOnAll job to zero hosts, the default is to fail the run (fail-loud — a typo in the selector shouldn’t silently skip work). Pass --target-allow-empty to skip the zeroed job instead; the job records a skipped status, and any downstream job that needs it with when: 'on-skip' (or when: 'always') still runs. See Job dependencies for the when gating model.

Exit codes:

CodeMeaning
0Every run of every matched workflow passed
1One or more runs failed, or a run was refused

kici run remote sends a run either through the KiCI Platform or straight to an orchestrator. It picks the target in this order:

  1. The --orchestrator-url <url> flag (see Direct mode).
  2. KICI_ORCHESTRATOR_TOKEN, with KICI_ORCHESTRATOR_URL or the orchestrator saved by kici connect.
  3. The orchestrator saved by kici connect.
  4. Your Platform login, saved by kici login.

Through the Platform, a remote run is dispatched to your active organization — the one set with kici org use <org>, or overridden per-run with --org <id>. The org is resolved in this order:

  1. The --org <id> flag, if provided.
  2. Otherwise the active org saved in your global config by kici org use <org>.
  3. If neither is set, the command errors and asks you to select an org with kici org use or pass --org.

The orchestrator anchors the org without any manual webhook source: it auto-provisions a system-managed remote source (routing key remote:<orgId>) that maps to its bound organization, so even a zero-source org is immediately routable for remote runs. You never set a routing key for a remote run — selecting the org is enough.

When an org has more than one connected orchestrator cluster, the CLI picks the target cluster in this order:

  1. The --orchestrator <name> flag, if provided.
  2. Otherwise the per-org default cluster, set with kici orchestrators use <name>.
  3. If the org has exactly one connected orchestrator, it is auto-selected.
  4. Otherwise the run errors with the list of connected clusters, and you pass --orchestrator <name> to choose one. Run kici orchestrators list to see the available cluster names.

kici run remote uses two independent paths:

  • Control plane — run initiation, trigger, status, log retrieval, and cancellation flow from your machine through the Platform, which relays them over a WebSocket connection to the org’s orchestrator. In direct mode they go to the orchestrator’s HTTP API instead, with the same requests and responses. The CLI polls for log chunks and run status until the run reaches a terminal state; there is no direct streaming socket to the orchestrator. The log position records how far each log file was read, so output from a step that starts later is never skipped.
  • Data plane — your working-tree overlay tarball uploads directly from your machine to the orchestrator’s object store via a presigned PUT URL. The overlay never passes through the Platform. This is why the orchestrator’s object-store upload endpoint must be reachable from your machine; see Storage layout.

In direct mode, kici run remote talks to an orchestrator’s own HTTP API (/api/v1/test) and skips the Platform. You need no KiCI Platform account. It works with an orchestrator in any mode: independent, hybrid, observed or platform.

Ask the orchestrator’s operator for a personal orchestrator token (kici-admin token create <label> --role admin --subject <you>), then save the target:

Terminal window
kici connect https://ci.example.com # prompts for the token
echo "$TOKEN" | kici connect https://ci.example.com --token-stdin
kici run remote push-main # runs on that orchestrator

For a single run, or in CI, pass the target without saving it:

Terminal window
KICI_ORCHESTRATOR_TOKEN=... kici run remote push-main --orchestrator-url https://ci.example.com

KICI_ORCHESTRATOR_URL selects a target only together with KICI_ORCHESTRATOR_TOKEN. The agent and the orchestrator read KICI_ORCHESTRATOR_URL as their own socket address, so a machine that runs one often has it set. The URL alone never moves a run off the Platform.

What changes in direct mode:

  • Who you are. The CLI first asks the orchestrator who the token belongs to, and prints the caller, its role and the orchestrator mode. An auditor token can follow runs but cannot start them, so the run stops before the upload.
  • Which organization. The orchestrator chooses it: its default organization when independent, or the organization the Platform assigned it otherwise. --org and --orchestrator are ignored, with a warning.
  • Storage. The overlay still uploads straight to the object store. The orchestrator signs the upload address from KICI_STORAGE_UPLOAD_ENDPOINT (then KICI_STORAGE_EXTERNAL_ENDPOINT, then KICI_STORAGE_ENDPOINT). If your machine cannot reach that address, the error names it and the setting.
  • Approval holds. On an independent orchestrator you answer a hold inline (or with --approve-all) through the orchestrator’s held-run routes; the token needs the owner or admin role. The orchestrator records an --approve-all approval as an automatic approval (held_run.auto_approve in its access log) by the token’s subject, and still checks each hold’s eligibility. On a Platform-connected orchestrator, holds are answered through the Platform (the dashboard, kici approve, or a PR comment), and the CLI says so once.

Direct mode leaves runs routed through the Platform unchanged. Run kici disconnect to forget the saved orchestrator, and kici doctor to see which target is active.

For executing workflow steps on your own machine without an orchestrator (no scaler, agents, or environments), use kici run <event> --local.

kici run remote works even if the repo has never been pushed to GitHub, because no run depends on a git remote. Every kici run remote run behaves the same way, with or without a remote:

  • The entire working tree is uploaded, including the .git directory, so steps that run git commands work
  • The lock file is sent inline (no GitHub API fetch)
  • The build cache is skipped: no __build__ job runs, and no cached source or dependencies are used
  • A bound context that does not set allowLocalExecution: true (the default is false) is skipped for the run, and the run shows a warning that names it

Destination routing is unchanged for fresh repos: the run goes to the same target as any other run (your active org through the Platform, or the direct orchestrator).

For a detailed guide on writing fixtures, configuring secrets, and understanding the upload flow, see Testing guide.

List the orchestrator clusters connected to an organization, and set the per-org default cluster used by kici run remote. Requires kici login and an active org (or pass --org).

Terminal window
kici orchestrators list [--org <id>]
kici orchestrators use <clusterName> [--org <id>]

kici orchestrators list prints the org’s connected orchestrator clusters, so you know what to pass to --orchestrator (or to kici orchestrators use).

kici orchestrators use <clusterName> sets the default orchestrator cluster for the org, stored per-org in your global config. Subsequent kici run remote invocations target that cluster unless overridden with --orchestrator.

Examples:

Terminal window
# List the active org's connected clusters
kici orchestrators list
# List a specific org's clusters
kici orchestrators list --org xyz789ghi012
# Set the default cluster for the active org
kici orchestrators use us-east
# Set the default cluster for a specific org
kici orchestrators use us-east --org xyz789ghi012

Inspect and manage execution runs from the terminal — the equivalent of the dashboard Runs page. All kici runs subcommands read/write the same org-scoped data as the dashboard, so they require kici login and an active org (kici org use <name>).

List runs with optional filters. Output is a table (run id, workflow, status, branch, trigger, started, duration); pagination is reported at the bottom.

A run can span several workflows: the shared run that a kici older than 0.21.0 gets for a fixture with no --workflow. Its workflow cell shows the first workflow and a count of the others (hello-world +3). --workflow matches a run that spans the named workflow. An archived run matches its first workflow only. In --json, each run carries workflowNames, every workflow with the first one first, next to workflowName.

When no runs match, kici runs list checks whether any webhooks arrived recently. If deliveries came in but nothing produced a run, it prints a one-line summary (“3 webhooks received in the last hour, 0 matched”) and suggests kici preview push to test your triggers locally — the fast way to find a misconfigured trigger. When nothing arrived it prints “No runs found.”

Terminal window
kici runs list [options]
Terminal window
kici runs list
kici runs list --status running
kici runs list --workflow ci --branch main
kici runs list --json | jq '.runs[].runId'

Show a run’s summary header plus its jobs-and-steps tree (name, status, duration, exit code). A run that never started a step also prints why: the run-level failure, and per job the reason it did not run — a job a context rejected names the context and the rule. Any approval hold on the run is listed too, with its context, hold type, status and reason. If the run id is not on the Platform but exists in your local run history (from kici run <event> --local), the local record is shown instead.

kici runs show, kici runs logs, kici runs rerun and kici runs cancel take a full run id or its first characters (at least 4), like a git short SHA. A prefix that matches several runs fails and lists them, so type more characters. A prefix that matches none, or text that cannot start a run id, fails and says so. A prefix also matches archived runs and runs in your local run history. kici runs list shows full run ids. To cancel on an orchestrator directly (--orchestrator-url), give the full run id: the orchestrator does not look up a prefix.

Steps are listed in the order they ran: a job’s image build, then its init commands, then its steps. An init command is named init[<n>], where <n> is its position in the job’s init list. kici runs logs uses the same order and names in its headings. An init command that an older orchestrator recorded with no step type is listed and named the same way.

In --json, each step in detail.jobs[].steps carries phase: setup (the job’s image build), init (an init command) or steps. An init command also carries initIndex, its position in the job’s init list. stepIndex is the stored index. For an init command it is a large internal number, so read phase and initIndex instead. The steps are in the order they ran, the same as in the text output. This is also true for a run that an older orchestrator recorded.

For a run that spans several workflows, the header shows workflows= with every workflow name, and each job is named <workflow> / <job>. In --json, each job in detail.jobs carries workflowName.

A failed job prints an error: line: the job’s own error message, or else the step that failed and its exit code (error: step test exited with code 3). A failed init command is named init[<n>] in this line too.

For a run started with kici run remote, the Platform reads the jobs and steps from the orchestrator cluster that ran the run. When the Platform cannot get them from that orchestrator, it shows the jobs it stored, without steps. The command then prints a note on standard error that names the reason, for example:

step detail unavailable: the Platform could not read it from the orchestrator that ran this run (no orchestrator that can serve this run is connected)

Standard output and the exit code do not change. See Troubleshooting for each reason.

Terminal window
kici runs show <run-id> [options]
Terminal window
kici runs show abc123
kici runs show abc123 --json

Print each job/step’s log lines in order, with headers. Each job starts with its setup log, under a (setup) heading, when it wrote one: the clone, the .kici dependency install and the workflow module load. The parentheses keep it apart from a step named setup. When a job’s setup log has no lines, a note under the heading tells the two cases apart: (no setup log recorded for this job) or (the setup log for this job is empty). When the orchestrator does not report the difference, no setup heading prints for that job. The --json output carries no note.

--json prints an object that maps each key to an array of lines. A step’s key is <job>/<step>. A job’s setup lines, when it wrote any, are under setup:<job>, with % and / in the job name written as %25 and %2F. A setup key never contains a / and a step key always does, so a step named setup cannot collide with it:

{
"setup:build": ["[host-install] Dependencies installed"],
"build/setup": ["step output"],
"setup:deploy%2Fprod": ["[host-install] Dependencies installed"],
"deploy/prod/apply": ["step output"]
}

kici runs logs prints the same step-detail note as kici runs show. When a job’s setup log cannot be read, a note on standard error names the job and the error. Standard output and the exit code do not change.

Terminal window
kici runs logs <run-id> [options]
Terminal window
kici runs logs abc123
kici runs logs abc123 --job build
kici runs logs abc123 --follow

Re-trigger a completed run. The server enforces a short cooldown between reruns of the same run. The cooldown also starts when the Platform cannot confirm the start.

When you run the command again within 30 minutes of a rerun that was not confirmed, the Platform sends the first request again. You get the run that the first request started, and no second run starts. The command then prints Rerun already queued by an earlier request: <id>, and --json output contains "alreadyAccepted": true. This applies to the same user or API key only.

The command prints the new run id as soon as the orchestrator accepts the rerun. The run then starts. If it cannot start, it fails, and kici runs show <id> shows why. If the Platform cannot confirm the start in time, the command says so and exits with code 1. The run can still start, so look for it before you retry: kici runs list --trigger rerun --since <time>. The command prints this command with the time filled in.

The rerun builds the same commit as the original run and goes through the same steps: jobs wait for their needs, contexts and their approval gates apply, and jobs can use artifacts and the cache. It runs the workflow’s declared jobs; jobs a generator produced are not run again. A rerun of a pull request from a fork goes through the organization trust policy again, so it can be held for approval as the first run was. When the policy now ignores it, the rerun is refused. A run cannot be rerun when the event it ran for is no longer stored: a webhook delivery past the audit retention window, or an internal event that has expired. A run started by a scaler event cannot be rerun either. Start a new run instead.

Terminal window
kici runs rerun <run-id> [options]
Terminal window
kici runs rerun abc123

Cancel a single run, or all in-progress runs on a branch.

Terminal window
kici runs cancel [run-id] [options]
Terminal window
kici runs cancel abc123
kici runs cancel abc123 --force
kici runs cancel --branch feature/wip

To cancel a run on an orchestrator directly, for example a run on the local dev plane, pass --orchestrator-url <url> or set KICI_ORCHESTRATOR_TOKEN (with KICI_ORCHESTRATOR_URL or a saved kici connect URL), the same way kici run remote picks its target. The command then calls that orchestrator’s admin API, so the token needs the run.cancel permission. A target saved with kici connect is used on its own only when you are not logged in to the Platform. --branch always goes through the Platform, and it cannot be combined with --orchestrator-url.

Terminal window
KICI_ORCHESTRATOR_URL=http://127.0.0.1:4319 \
KICI_ORCHESTRATOR_TOKEN="$(cat ~/.kici/local/admin-token)" \
kici runs cancel 0f1e2d3c-4b5a-4c6d-8e7f-0123456789ab --force

List the artifacts a run uploaded — name, producing job, size, content hash, and creation time. An artifact whose stored object can no longer be reached is flagged unavailable.

Terminal window
kici runs artifacts list <run-id> [options]
Terminal window
kici runs artifacts list abc123
kici runs artifacts list abc123 --json

Download a run’s artifacts. Name one to fetch just that artifact; omit the name to download every artifact of the run. Each artifact extracts into its own <name>/ directory by default.

Terminal window
kici runs artifacts download <run-id> [name] [options]
  • --archive — save the raw .tar.gz as <name>.tar.gz instead of extracting.
  • -o, --output <dir> — write into <dir> instead of the current directory.
Terminal window
kici runs artifacts download abc123 bundle
kici runs artifacts download abc123 bundle -o ./out
kici runs artifacts download abc123 --archive
kici runs artifacts download abc123

The download streams directly from object storage over a short-lived signed URL — the artifact bytes never pass through the KiCI Platform — and the content hash is verified end to end, so a corrupted or truncated transfer fails loudly rather than leaving a bad file on disk.

Artifacts expire after the orchestrator’s configured retention. When you name a single artifact whose stored object is already gone, the command fails. When you download the whole run, such an artifact is reported as a warning and skipped so the remaining artifacts still land — the command fails only if every artifact of the run was unreachable. Any other failure (a rejected signed URL, a content-hash mismatch) stops the command immediately rather than continuing with the rest.

Artifact names are case-sensitive, so a single run can hold both bundle and Bundle. On a filesystem that ignores case in path lookups — macOS and Windows default to this, Linux does not — both would land on the same path, so downloading the whole run refuses before writing anything and names the pair. Fetch them one at a time into separate directories instead:

Terminal window
kici runs artifacts download abc123 bundle -o ./bundle-lower
kici runs artifacts download abc123 Bundle -o ./bundle-upper

Naming a single artifact is never affected, and on a case-sensitive filesystem downloading the whole run still writes both.

Artifacts are packed relative to two roots: paths inside your repository and paths under the home directory. Repository-relative files land directly under <name>/; home-relative files land under <name>/~home/, so the two can never overwrite each other and nothing is ever written outside the output directory.

When --json is set on any of these commands, kici emits only the JSON document on stdout — the kici v<version> banner is suppressed — so the output is safe to pipe into jq or JSON.parse. The same holds for the other --json commands (kici run remote --json, kici workflows list --json) and for --quiet.

Reject a held approval gate. A rejection fails the held element and the run. A reason is required.

Terminal window
kici reject <run-id> --reason <text> [options]

Examples:

Terminal window
# Reject a held job with a reason
kici reject abc123 --job deploy-production --reason "Wrong release branch"

Approve a held approval gate so the run resumes. Identify the held element by run ID, optionally narrowed to a job and step.

Terminal window
kici approve <run-id> [options]

Examples:

Terminal window
# Approve a workflow-level hold
kici approve abc123
# Approve a held job
kici approve abc123 --job deploy-production
# Approve a held step (steps are addressed by index)
kici approve abc123 --job migrate-and-deploy --step 1

You must be eligible for at least one unsatisfied clause (a member of a named team, or a named user) and hold the permission that matches the hold’s type — ci_trust:write for a security hold, contexts:admin for a wait-timer hold, contexts:write for every other hold. The server enforces this, so the same rule applies to the dashboard and to an AI agent’s tools. The command reports whether the element was released, how many clauses remain, or that it was rejected.

Approve a held approval gate for a run

Synopsis: kici approve <run-id> [options]

Arguments

ArgumentRequiredVariadicDescription
run-idyesnoRun ID whose approval gate to approve

Options

OptionDefaultDescription
--job <name>Approve the hold for a specific job
--step <index>Approve a step-scoped hold (requires —job)
--hold-type <type>Approve the hold of this type (reviewer, timer, concurrency, security) — a job can carry two
--hold <id>Approve one hold by its id, as listed when nothing else separates them

Reject a held approval gate for a run

Synopsis: kici reject <run-id> [options]

Arguments

ArgumentRequiredVariadicDescription
run-idyesnoRun ID whose approval gate to reject

Options

OptionDefaultDescription
--job <name>Reject the hold for a specific job
--step <index>Reject a step-scoped hold (requires —job)
--hold-type <type>Reject the hold of this type (reviewer, timer, concurrency, security) — a job can carry two
--hold <id>Reject one hold by its id, as listed when nothing else separates them
--reason <text>Reason for the rejection

Execute workflows locally or remotely

Synopsis: kici run [event] [options]

Arguments

ArgumentRequiredVariadicDescription
eventnonoEvent type for a routed local run (e.g. push, pr:open)

Options

OptionDefaultDescription
--localfalseRoute the run with this machine as the ephemeral agent
--offlinefalseForce the throwaway/independent plane (offline)
--connectedfalseForce the connected/hybrid plane (requires attachment)
--in-placefalseReuse the working tree directly instead of an isolated clone
--trustedfalseRoute to the trusted fleet agent profile: steps see the ambient host env (minus the agent identity). Alias: —no-sandbox
--no-sandboxAlias for —trusted (the bwrap sandbox is already off by default)
--env <KEY=VALUE>Per-run secret (repeatable)
--payload <path>Dispatch payload JSON { action?, client_payload? } for a routed dispatch run
--kici-dir <path>.kiciPath to .kici directory
--quietfalseSuppress the banner + streaming output
--debugfalseVerbose internals

Execute fixtures remotely via orchestrator

Synopsis: kici run remote [fixture] [options]

Arguments

ArgumentRequiredVariadicDescription
fixturenonoFixture name or glob pattern (omit to list available)

Options

OptionDefaultDescription
--workflow <name>Run only this workflow, bypassing triggers (with a fixture: send its event to this workflow)
--allfalseRun all available fixtures
-p, --pickfalseInteractively pick fixtures to run
--parallelfalseRun matching fixtures concurrently
--no-waitFire and forget (print runIds, don’t stream)
--quietfalseSuppress output except final result
--jsonfalseOutput structured JSON result
--junit <path>Output JUnit XML result
--historyfalseShow recent run history
--routing-key <key>Deprecated; has no effect (the orchestrator chooses the routing key)
--orchestrator-url <url>Run on this orchestrator directly, skipping the Platform (token: KICI_ORCHESTRATOR_TOKEN or kici connect)
--org <id>Target organization (overrides the active org)
--orchestrator <name>Target orchestrator cluster (overrides the per-org default)
--debugfalseVerbose internals
--kici-dir <path>.kiciPath to .kici directory
--context <ctx.key=value>Inject a namespaced context secret, uploaded encrypted to the orchestrator (repeatable)
--env <KEY=VALUE>Provide a per-run secret (repeatable); uploaded encrypted to the orchestrator
--checkfalseRun in check mode: report drift, change nothing
--fail-on-driftfalseIn check mode, exit non-zero if any step reports drift
--target <selector>Narrow runsOnAll jobs to hosts matching this label selector (repeatable, AND-combined)
--target-allow-emptyfalseA —target that narrows a runsOnAll job to zero hosts skips it instead of failing
--input <KEY=VALUE>Typed workflow-dispatch input (repeatable)
--yes, --approve-allfalseAuto-approve every approval gate this run holds on (run-scoped; eligibility still enforced)

Inspect and manage execution runs

Synopsis: kici runs

List and download a run’s artifacts

Synopsis: kici runs artifacts

Download one artifact, or all of them — extracts by default

Synopsis: kici runs artifacts download <run-id> [name] [options]

Arguments

ArgumentRequiredVariadicDescription
run-idyesnoRun ID whose artifacts to download
namenonoArtifact name (omit to download every artifact of the run)

Options

OptionDefaultDescription
--archivefalseSave the raw .tar.gz instead of extracting
-o, --output <dir>Output directory (default: current directory)

List the artifacts a run uploaded

Synopsis: kici runs artifacts list <run-id> [options]

Arguments

ArgumentRequiredVariadicDescription
run-idyesnoRun ID whose artifacts to list

Options

OptionDefaultDescription
--jsonfalseOutput raw JSON

Cancel a run, or all in-progress runs on a branch

Synopsis: kici runs cancel [run-id] [options]

Arguments

ArgumentRequiredVariadicDescription
run-idnonoRun ID to cancel, or its first 4 or more characters

Options

OptionDefaultDescription
--forcefalseForce cancel (kill immediately, skip hooks)
--branch <name>Cancel all in-progress runs on this branch
--orchestrator-url <url>Cancel on this orchestrator directly (token from KICI_ORCHESTRATOR_TOKEN or kici connect)

List execution runs (mirrors the dashboard Runs page)

Synopsis: kici runs list [options]

Options

OptionDefaultDescription
--status <s>Filter by status
--workflow <w>Filter by workflow name
--branch <b>Filter by branch/ref
--repo <r>Filter by repository
--trigger <t>Filter by trigger type
--source <routingKey>Filter by source routing key, or by provider (e.g. github)
--since <ts>Only runs since (ISO-8601 or epoch ms)
--cursor <cursor>Keyset cursor for the next page (from a prior nextCursor)
--jsonfalseOutput raw JSON

Print step logs for a run

Synopsis: kici runs logs <run-id> [options]

Arguments

ArgumentRequiredVariadicDescription
run-idyesnoRun ID, or its first 4 or more characters

Options

OptionDefaultDescription
--job <name>Only logs for this job
-f, --followfalseTail logs for a live run
--jsonfalseOutput raw JSON

Re-trigger a run

Synopsis: kici runs rerun <run-id> [options]

Arguments

ArgumentRequiredVariadicDescription
run-idyesnoRun ID to rerun, or its first 4 or more characters

Options

OptionDefaultDescription
--jsonfalseOutput raw JSON

Show a run summary with its jobs and steps, why a job did not run, and any approval hold

Synopsis: kici runs show <run-id> [options]

Arguments

ArgumentRequiredVariadicDescription
run-idyesnoRun ID to inspect, or its first 4 or more characters

Options

OptionDefaultDescription
--jsonfalseOutput raw JSON