Executed commands
Every command the build executed, read from the session a running buildnote monitor records on the runner. Gathering is dynamic rather than static: this is what the job actually ran, not what a pipeline file says it would run.
| Facts key | commands |
| Version | v1 |
| Script | commands.py |
| Timeout | 60 seconds |
Inputs
| Input | Description | Default | Environment |
|---|---|---|---|
sessionsDir | Directory holding the monitor sessions. When it names none, BUILDNOTE_MONITOR_DIR is read and the buildnote-monitor directory under both the runner's temporary directory and /tmp is looked in, which is where buildnote monitor records. Both are looked in because a JVM and a Python process do not always agree on which directory is the temporary one. | `` | GUARDRAIL_INPUT_SESSIONSDIR |
maxExecutions | How many executions to report before the rest are left out and counted in dropped. The ones kept are the earliest, so a command run near the start of the job survives a build that execs past the ceiling later on. | 1000 | GUARDRAIL_INPUT_MAXEXECUTIONS |
A guardrail that declares an input of the same name passes it through, so the guardrail's configuration in buildnote.json is what decides these values.
A guardrail asks for these facts by name, and reads them back the same way:
{
"collect": ["commands"]
}commands = guardrail.facts("commands")Facts
The document commands collects. A path carrying [] is an entry of the list before it, and one carrying [path] is a key of the object before it.
| Fact | Meaning |
|---|---|
source | report when a monitor session was read, none when there was none. |
reason | Why there was none, present only when source is none. |
reports | The session that was read. |
reports[].path | Absolute path of the session file on the runner. A monitor records outside the checkout, so this is the one path in the facts that is not stated from the directory the CLI runs in. |
reports[].modified | When it was last written, ISO 8601. A session still being appended to was written seconds ago. |
reports[].format | Always monitor-session. |
monitor | The monitor whose session was read. |
monitor.running | Whether the monitor is still recording, which is a session carrying no closing record. false means the monitor finished and the commands are the whole of what it traced. |
monitor.matched | How the session was chosen: build when it records the build this run belongs to, which is the case whenever the monitor and the guardrails run in the same job, and recent when no session names this build and the most recent one was read instead. |
monitor.sessions | How many sessions were found across the directories that were looked in. More than one means another monitor has recorded on this runner. |
monitor.pid | Process the monitor is running as. |
monitor.startedAt | When the monitor started recording, epoch milliseconds. |
monitor.closedAt | When it stopped, epoch milliseconds, or null while it is still recording. |
monitor.build | Build the monitor recorded for, which is what matched compares against. |
monitor.module | Module the monitor submits its commands under. |
monitor.command | The command the monitor launched, or null when it attached to a process it did not start. |
commands | Every command that was executed, one entry per execution, in the order they were executed. A command run twice is two entries. |
commands[].command | The command line, exactly as the process was executed, truncated to 2000 characters. Arguments are separated by single spaces and their original quoting is gone: this is the process's argument vector joined, not the shell line that produced it. |
commands[].pid | Process the command ran as. A pid is reused within a job, so it identifies an execution only alongside its timestamp. |
commands[].startedAt | When it was executed, epoch milliseconds. |
commands[].durationMs | How long it ran, milliseconds, or null when it was not seen to exit. |
commands[].exitCode | The code it exited with, or null when it was not seen to exit, which is a command still running or one the monitor stopped tracing before it finished. |
counts.executions | How many commands were executed in total, including any left out by maxExecutions. |
counts.commands | How many distinct command lines are among the executions reported. |
dropped | Executions left out because maxExecutions was reached. Above zero the list describes part of the build: a command found here is real, but a command absent from it is not proof the build never ran it, so a check that would otherwise report clean must skip. |
A collector that cannot finish prints what it had along with an incomplete key saying why, so the facts below the point it stopped are absent. A check that cares reads incomplete before the rest.
Example facts
The example facts collected by commands collector:
{
"source": "report",
"reports": [
{
"path": "/home/dana/widget/monitor-sessions/4242.ndjson",
"modified": "2026-03-04T10:15:00Z",
"format": "monitor-session"
}
],
"monitor": {
"running": true,
"matched": "recent",
"sessions": 1,
"pid": 4242,
"startedAt": 1757500000000,
"closedAt": null,
"build": "18342119027_1",
"module": "build",
"command": null
},
"commands": [
{
"command": "git rev-parse HEAD",
"pid": 4310,
"startedAt": 1757500001000,
"durationMs": 200,
"exitCode": 0
},
{
"command": "./gradlew check",
"pid": 4311,
"startedAt": 1757500002000,
"durationMs": null,
"exitCode": null
},
{
"command": "claude -p --output-format json --allowedTools Read 'review this diff'",
"pid": 4327,
"startedAt": 1757500090000,
"durationMs": 50000,
"exitCode": 0
},
{
"command": "git rev-parse HEAD",
"pid": 4402,
"startedAt": 1757500150000,
"durationMs": 200,
"exitCode": 0
}
],
"counts": {
"executions": 4,
"commands": 3
},
"dropped": 0
}Collected for
| Guardrail | Category | Inputs |
|---|---|---|
claude/cli-safe-flags | claude | none |
claude/cli-structured-output | claude | none |