v2.0.0next
Chain file keys

Subprocess tasks

What Kraft passes a subprocess task, how it runs, and how its exit code and result file become a status.

A kind: subprocess task runs one command in the item's worktree. This page is the contract between that command and Kraft. For the task's keys, see Chain file keys.

The command

config/library.yaml
tasks:
  lint:
    kind: subprocess
    command: sh -c 'npm run lint && npx tsc --noEmit'

Kraft does not run command through a shell. It splits the string into arguments the way a shell would (Python's shlex.split) and runs the first one directly. So &&, ||, pipes, redirects and $VAR do nothing on their own: npm run lint && npx tsc runs npm with && as an argument. Wrap anything that needs a shell in sh -c '...'. A command Kraft cannot split, such as one with an unclosed quote, stops the item before it runs.

Where it runs

  • Working directory. The item's worktree. A scope: each_repository task on a workspace item runs once per selected repository, in that repository's checkout and with its repos.yaml entry, and stops at the first run that does not end done.
  • Input. stdin is /dev/null.
  • Output. stdout and stderr go to the session log. Read it with kraft view logs ID --session SESSION.
  • Processes. The command gets a process group of its own. When it exits, Kraft sends SIGTERM to whatever it left running in that group, then SIGKILL 10 seconds later.
  • Sandbox. In a sandboxed repo, the command runs inside the sandbox like every other task, and its result file is mounted into the container.

Environment

The command gets the worker allowlist and the repo's env and env_passthrough (see Environment variables), plus:

VariableValue
KRAFT_RESULT_PATHThe session's result file. Writing it is optional.
PYTHONDONTWRITEBYTECODE1, so a fix loop's re-run never imports bytecode left over from the code before the fix.
GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, GIT_COMMITTER_NAME, GIT_COMMITTER_EMAILThe server's own values when it has them, otherwise user.name and user.email from the repo's git config. The repo's env wins over them.

Kraft does not set KRAFT_WORK_ITEM_ID or KRAFT_SESSION_ID for a subprocess task.

How the status is decided

A result file, when there is one, wins over the exit code.

What happenedStatus
The command wrote a result file with a known statusThat status. See Result file.
The result file has no status, an unknown one, or is not valid JSONfailed
No result file, or an empty one, and exit code 0done
No result file, or an empty one, and any other exit codefailed
The command or the working directory does not existconfig_error: the item stops and waits for you, and no fix loop runs
The command cannot be splitconfig_error
A time cap ran outcapped_out: the process group is killed and the item stops and waits for you
You paused the itempaused

What each status does to the node:

StatusWhat the node does
done, done_with_concernsThe node goes on.
failedThe node goes to its recovery, then its fix loop.
needs_contextThe item stops and waits for you, with the result file's question.

What a fix loop sees

A failing subprocess task that reports no findings becomes one critical finding. It holds:

  • the task's path and the command that ran;
  • up to five lines from the last 20 KB of its output: the first five lines marked FAILED, Error:, Traceback or ✘ if there are any, otherwise the last five; timings, timestamps and URLs are removed and the whole is cut at 400 characters;
  • Confirm the fix with: and the command;
  • the kraft view logs ID --session SESSION command for the full log.

A command that writes findings into its result file reports those instead, in the same shape a review agent uses. See Result file.

Copyright © 2026