v1.4.0next
Chain nodes

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 nodes.

The command

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 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 for you
You paused the itempaused

done and done_with_concerns let the node go on. failed sends the node to its recovery and fix loop. needs_context stops the item 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 end of its output: the lines marked FAILED, Error:, Traceback or ✘ if there are any, otherwise the last five;
  • 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