Background jobs that run a command, what they are allowed to do, and what the errors mean.
Minions is the brain's background job queue. A job is a row in the brain's database with a name, some JSON parameters and a status. A worker process picks up waiting jobs, runs the handler registered for that name, and records the result, retrying on failure. Because the queue lives in the database, jobs survive restarts.
A shell job is a job that runs a command on the worker machine. Use it to move scheduled scripts, such as an API fetch or a scrape that writes into the brain, onto the queue so they get retries, timeouts, status and a record of what ran. Because a shell job can run anything the worker's user can run, it is switched off unless the worker is started with NEXUS_BRAIN_ALLOW_SHELL_JOBS=1.
nexusbrain CLI is installed and a brain is set up on the machine that will run the commands.nexusbrain jobs work or nexusbrain jobs supervisor), the brain must use Postgres. On the local PGLite engine, run each shell job inline with --follow instead.cmd form runs through /bin/sh -c, so it needs a system with /bin/sh, such as macOS or Linux. The argv form starts the program directly.NAME=value command).You describe the command in the job's parameters, passed as JSON with --params. Give exactly one of cmd or argv, and always an absolute cwd.
PATH, HOME, USER, LANG, TZ and NODE_ENV from the worker, then any inherit values, then your env values on top. Other variables of the worker, such as API keys, are not passed.inherit name becomes an upper-case variable: anthropic_api_key becomes ANTHROPIC_API_KEY. The one exception is database_url, which becomes NEXUS_BRAIN_DATABASE_URL.exit_code, stdout_tail, stderr_tail, duration_ms and pid. The last 64 KB of output and the last 16 KB of errors are kept.exit <code>: followed by the end of the error output. Failed attempts are retried, 3 attempts in total by default.| Parameter | Type | Meaning |
|---|---|---|
cmd | string | A shell command line, run with /bin/sh -c. Use it when you need pipes, redirects or &&. |
argv | array of strings | The program and its arguments, started directly with no shell. Safer when arguments contain spaces or quotes. |
cwd | string, required | Absolute path of the working directory. This is the trust boundary: choose a directory the script is meant to work in. |
env | object of strings | Extra environment variables for the command. Values are stored in the job row as plain text. |
inherit | array of names | Config key names, such as anthropic_api_key, whose values the worker reads from its own config and passes to the command. Only the names are stored, never the values. |
redact_secrets | boolean | When true, values pulled in through inherit are replaced with <REDACTED:name> in the saved output. The CLI flag --redact-secrets sets it for you. |
The simplest way to run a shell job is inline: --follow submits the job and runs it in the same terminal, then prints the result. This works on every engine, including PGLite. The switch must be set on the process that runs the job, which with --follow is the command itself.
A dry run only prints what would be submitted. It does not check the parameters.
nexusbrain jobs submit shell --params '{"argv":["git","pull"],"cwd":"/home/me/notes"}' --dry-runYou should see"[DRY RUN] Would submit job:" followed by the name, queue, priority, max attempts and the data.
NEXUS_BRAIN_ALLOW_SHELL_JOBS=1 nexusbrain jobs submit shell --params '{"argv":["git","pull"],"cwd":"/home/me/notes"}' --followYou should see"Job #<id> submitted (shell). Executing inline...", then "Job #<id> completed in <n>s" and a Result line with exit_code 0 and the command output.
cmd when you need shell features.--timeout-ms sets a wall-clock limit for the job in milliseconds. When it runs out, the command gets SIGTERM, then SIGKILL 5 seconds later.
NEXUS_BRAIN_ALLOW_SHELL_JOBS=1 nexusbrain jobs submit shell --params '{"cmd":"./fetch.sh > latest.json","cwd":"/home/me/scripts"}' --follow --timeout-ms 600000You should seeThe job completes, or ends with an "aborted" error if it ran past the timeout.
The inline worker only knows the built-in job types. It cannot run custom job types that a host app registers.
For scheduled work, submit jobs without --follow and let a long-running worker pick them up. This needs the Postgres engine. The switch belongs on the worker, not on the machine that submits.
NEXUS_BRAIN_ALLOW_SHELL_JOBS=1 nexusbrain jobs workYou should seeThe worker starts and prints "[minion worker] shell handler enabled (NEXUS_BRAIN_ALLOW_SHELL_JOBS=1)".
--allow-shell-jobs sets NEXUS_BRAIN_ALLOW_SHELL_JOBS=1 on the worker the supervisor starts. Check it later with nexusbrain jobs supervisor status, and stop it with nexusbrain jobs supervisor stop.
nexusbrain jobs supervisor start --detach --json --allow-shell-jobsYou should seeA JSON line with the supervisor PID and PID file, and the command returns.
An idempotency key stops the same run from being queued twice, which helps when a scheduler fires more than once. Other useful options: --max-attempts, --backoff-type fixed|exponential, --backoff-delay, --priority and --queue.
nexusbrain jobs submit shell --params '{"argv":["/usr/bin/python3","sync_calendar.py"],"cwd":"/home/me/scripts"}' --idempotency-key calendar-2026-09-17T09:00You should seeThe new job printed as JSON, including its id, plus a reminder that shell jobs need NEXUS_BRAIN_ALLOW_SHELL_JOBS=1 on the worker process.
A worker started without the switch still picks up shell jobs, but refuses them: the job ends as dead with "shell handler disabled on this worker (set NEXUS_BRAIN_ALLOW_SHELL_JOBS=1 to execute shell jobs)". If no worker is running at all, the job stays waiting.
Every shell job submission is also written to an audit file, ~/.nexusbrain/audit/shell-jobs-YYYY-Www.jsonl, one file per week. Set NEXUS_BRAIN_AUDIT_DIR to keep it elsewhere. Each line has the job ID, the working directory, the first 80 characters of the command and any inherit names, but never env values. It is a debugging trail, not a security record: if writing it fails, the job is still submitted.
Filter with --status, for example --status dead, or with --queue.
nexusbrain jobs list --limit 20You should seeA table with ID, Name, Status, Queue, Time and Created.
nexusbrain jobs get <id>You should seeA header such as "Job #42: shell (COMPLETED)", the attempts, then a Result line with the output tails, or an Error line and a History of attempts.
nexusbrain jobs retry <id>You should see"Job #<id> re-queued for retry."
nexusbrain jobs cancel <id>You should see"Job #<id> cancelled." A running command is stopped with SIGTERM, then SIGKILL after 5 seconds.
nexusbrain jobs statsYou should see"Job Stats (last 24h):" with totals per job type and a Queue health line with waiting, active and stalled counts.
Shell jobs are remote code execution by design, so the protection comes from who can submit them and what the worker is allowed to reach.
nexusbrain as the worker's user, with NEXUS_BRAIN_ALLOW_SHELL_JOBS=1 on the worker, can run any command that user can run. Turn the switch on only on workers that need it.cwd with care.cmd, argv or env. Those are stored in the job row, and the start of cmd is written to the audit log. Put the secret in the worker's config and name it in inherit instead.--redact-secrets. It hides inherit values in the saved output. It does not hide env values, and it cannot catch a secret that the script encodes before printing.Never set NEXUS_BRAIN_ALLOW_SHELL_JOBS=1 in a shared shell profile or a system-wide environment. Set it only on the worker process that should run commands.
Your parameters have both cmd and argv, or neither (an empty string or empty array counts as missing). Keep exactly one.
Every element of argv must be a string. Quote numbers too: ["sleep","5"], not ["sleep",5].
Add cwd with a full path such as /home/me/scripts. Relative paths like . or scripts and paths starting with ~ are rejected.
Give env as a JSON object, such as {"MODE":"daily"}, not an array or null.
Quote every value in env, for example {"RETRIES":"3"}.
Give inherit as an array, such as ["anthropic_api_key"], even for one name.
Remove empty strings and non-string values from inherit.
Use the config key name in lower case with underscores, such as anthropic_api_key, not the environment variable name ANTHROPIC_API_KEY.
The worker's config has no value for that key. Set it with nexusbrain config set <name> <value> or in the worker's config file, then submit again. The check runs against the config of the machine where you submit, and again on the worker when it runs.
Use true or false without quotes, or drop the field and pass --redact-secrets on the command line.
The worker that picked up the job was started without the switch, and the job was marked dead. Restart the worker with NEXUS_BRAIN_ALLOW_SHELL_JOBS=1 (or the supervisor with --allow-shell-jobs), then run nexusbrain jobs retry <id>.
The local PGLite engine cannot run a separate worker. Run each job inline with NEXUS_BRAIN_ALLOW_SHELL_JOBS=1 nexusbrain jobs submit shell --params '...' --follow, or move the brain to Postgres.
Wrap the JSON in single quotes so the shell leaves the double quotes alone, and check for trailing commas.
The command itself failed. Read the full error with nexusbrain jobs get <id>. Remember the command only gets PATH, HOME, USER, LANG, TZ and NODE_ENV from the worker; pass anything else it needs through env or inherit.