Private Beta
.md

BountyHub Docs

Steps

Running scripts and processes with step.script and step.exec, and moving files between activities with artifacts.

Steps are the units of execution inside an activity. This page covers the two execution primitives (step.script and step.exec) and artifact plumbing. Other step kinds are covered in their own pages:

#Common Step Parameters

Most steps accept:

ParameterDefaultMeaning
id—Stable key for run.steps.<id> lookups and scan.succeeded() predicates
name—Human-facing label shown in the UI (defaults are derived)
condrun.okRun the step only when this runtime predicate holds
allow_failureFalseDo not fail the activity if the step fails
timeout—Bound the step's execution time

step.spawn is the exception: it returns a branch handle, so it accepts name and timeout only (its allow_failure parameter is accepted but ignored).

BHW

#Conditions: cond, .when(...) and step predicates

A condition is a runtime predicate F(run) -> bool. Write it as a lambda or pass a step predicate. It is evaluated by the runner just before the step executes, never while the workflow is being defined:

BHW
  • scan.succeeded() / scan.failed() return a predicate run -> bool; they require the referenced step to have an id.
  • .when(F) and cond = F are the same mechanism. Static booleans are rejected (cond = True, .when(True)) so a condition can never be evaluated too early.
  • A step predicate can only test an earlier step in the same activity. Cross-activity wiring is trigger.on(scan.succeeded()) — that is an activity event, not a step condition.

Steps can be bound to variables so later conditions can inspect them via run.steps (see the Syntax Reference).

#Shell Scripts: step.script

step.script is the explicit shell API. It runs a script under a shell (bash by default):

BHW

Script content can also be a function, which is evaluated at execution time with the run context — useful for templating from dispatch inputs or earlier results:

BHW

#Process Execution: step.exec

step.exec runs a program directly from a complete argv vector. There is no implicit shell: element 0 is the program, and the process receives exactly what you declare.

BHW

#Explicit everything

step.exec has no implicit workflow data. The process receives exactly the explicitly provided command, environment, stdin, and working directory:

  • command — the complete argv vector. Elements may be literal strings, references to files produced by earlier steps, config.* values, module string inputs, or lazy runtime expressions. Module file and directory inputs cannot be used as raw command elements; format them into argv (for example "{}".format(targets)) instead. A nested list is an optional argv group: it expands only when every optional module input it references is bound, otherwise the whole group is dropped. A bare optional module input element is dropped when unbound — so CLI flags vanish instead of being passed empty. Concatenating a string literal with a bool module input using + is rejected; use a conditional instead.
  • env — only the entries you list. Values may be config references; secret values are resolved at execution time and never serialized.
  • stdin / stdout / stderr — optional; each must be a path.file(...). Without an explicit stdout, process output is captured for logs but never becomes workflow data.
  • cwd — optional; must be a path.directory(...).
BHW

A non-zero exit code fails the step (unless allow_failure = True). Timeouts are configured per step with timeout; there is no per-step retry policy.

#Data flow defines ordering

There is no implicit sequencing between independent processes beyond the order you write them in. When a step's command, stdin, or environment references a resource produced by an earlier step, that reference is both the data flow and the scheduling order — the runner waits for the producer first:

BHW

Each step consumes the previous step's output file, so they execute strictly in order. Steps whose inputs are unrelated to each other should be declared as parallel branches instead (see Concurrency).

Optional inputs make reusable commands clean. Inside a module, an unbound optional input (and any nested list group containing only unbound optionals) is omitted from argv rather than passed as an empty string:

BHW

#Artifacts

Activities are isolated: files produced by one activity are not automatically visible to the next. Artifacts are the way to hand files between activities.

#Uploading: step.upload_artifact

BHW
  • name names the artifact so downstream activities can reference it via discover.artifacts.subdomains.
  • entries map files from the activity workspace (source) into the artifact layout (destination).
  • expires_in controls retention — useful for noisy raw scan output you do not need to keep long.

#Downloading: step.download_artifact

BHW
  • step.download_artifact(artifact) pulls a previously uploaded artifact into the current activity workspace. There is no separate blob-store import primitive: shared wordlists, nuclei templates, or resolver lists travel through artifacts.

#Reading a dataset: step.download_dataset

BHW
  • step.download_dataset exports a project dataset's current logical state into the activity workspace ("csv" by default, or "jsonl"/"json"). Use it to diff new findings against knowledge from earlier runs. See Datasets.

#Choosing Between script and exec

Use step.script when...Use step.exec when...
You want pipes, redirection, variable expansionYou want a single tool invocation
Gluing multiple commands togetherPrecision matters: exact argv, exact env
Prototyping a phase quicklyBuilding reusable, typed steps

Both fail the activity on non-zero exit unless allow_failure = True.