New to FORGE? Start a free 30-day trial

Command Hooks

Updated on Oct 1, 2026

Command hooks run steps and checks before and after a workflow command, such as frg issue draft or frg ask. Use them to validate prerequisites, prepare the workspace, or verify outputs every time a command runs. Workflow steps, in contrast, orchestrate several commands; see Workflow Steps Reference.

How hooks work

  • You define hooks per operation in .forge/config/commands.toml, under [[commands.operations.hooks.pre]] and [[commands.operations.hooks.post]].
  • Each hook item is either a step (kind = "step") or a check (kind = "check").
  • Pre-hooks run in definition order before the agent starts.
  • Post-hooks run in definition order after the command succeeds. A failing post-hook doesn't fail the command, because the agent has already finished.
  • When a workflow runs a command, the command's hooks run as part of that command.
toml
[[commands]]
target = "ask"

[[commands.operations]]
name = "default"

# Runs before the command
[[commands.operations.hooks.pre]]
kind = "check"
id = "workspace-clean"
type = "command_success"
command = "git diff --quiet"
on_false = "skip"

[[commands.operations.hooks.pre]]
kind = "step"
id = "output-dir"
type = "builtin"
action = "make_directory"
path = "output"

# Runs after the command succeeds
[[commands.operations.hooks.post]]
kind = "step"
id = "list-output"
type = "shell"
command = "ls output"

Warning: frg config validate doesn't check hook items. An unknown action, check type, or missing field passes validation and fails only when the command runs. Test new hooks on a sample command.

Steps

Steps perform actions that can change the environment. Set type to builtin, shell, or agent.

Built-in steps

Built-in actions are cross-platform and don't start a shell. Prefer them for file operations.

ActionRequired fieldsDescription
make_directorypathCreate a directory, including parents.
make_filepathCreate an empty file.
remove_filepathDelete a file.
remove_directorypathDelete a directory recursively.
copy_filesource, destinationCopy a file.
write_filepath, contentWrite content to a file.
append_filepath, contentAppend content to a file.
set_envenv_name, env_valueSet an environment variable for the command and later hooks.
toml
[[commands.operations.hooks.pre]]
kind = "step"
id = "set-report-dir"
type = "builtin"
action = "set_env"
env_name = "REPORT_DIR"
env_value = "output/reports"

An export in a shell step affects only that shell invocation. Use set_env to pass a variable to the command and later hooks.

Shell steps

Shell steps run command. Use command_linux, command_macos, or command_windows for platform-specific variants:

toml
[[commands.operations.hooks.pre]]
kind = "step"
id = "install-jq"
type = "shell"
command_linux = "sudo apt-get install -y jq"
command_macos = "brew install jq"
command_windows = "choco install jq"

Warning: Shell steps run with your user permissions and can run any command. Don't interpolate untrusted input or put secrets in commands. Before you use shell hooks, read Safe Customization Guidelines.

Agent steps

Agent steps (type = "agent") send a prompt to the AI agent, and share_session_with reuses another step's session. They're mainly used in workflows. See Workflow Steps Reference.

Checks

Checks test a condition without changing state.

Check typeRequired fieldsPasses when
file_existspathThe file exists.
directory_existspathThe directory exists.
path_existspathThe file or directory exists.
file_containspath, contentThe file contains the substring.
file_matchespath, patternThe file matches the regular expression.
env_existsenv_nameThe environment variable is set.
env_equalsenv_name, env_valueThe environment variable equals the value.
command_existspath (command name)The command is found in PATH.
command_successcommandThe command exits with status 0.

on_false controls what happens when a check fails:

ValueBehavior
failStop with an error.
skipLog Check failed with on_false='skip', continuing execution and continue.
toml
[[commands.operations.hooks.pre]]
kind = "check"
id = "in-ci"
type = "env_equals"
env_name = "CI"
env_value = "true"
on_false = "skip"

Dependencies and conditions

FieldDescription
depends_onA step name or a list of names that must complete first. failed(<name>) means the named step or check must have failed.
requires_checkA check that must pass for the step to run.
toml
[[commands.operations.hooks.pre]]
kind = "step"
id = "ci-install"
type = "shell"
command = "npm ci"
requires_check = "in-ci"

[[commands.operations.hooks.pre]]
kind = "step"
id = "local-install"
type = "shell"
command = "npm install"
depends_on = "failed(in-ci)"

Forge reports invalid references when the hooks run, for example depends_on references unknown step, requires_check references unknown check, circular dependency detected among steps, and failed() requires a step or check name.

Timeout and retry

FieldDescription
timeout_secondsStop the step if it runs longer than this.
retry_countNumber of retries after a failure.
retry_delay_secondsWait time between retries.
toml
[[commands.operations.hooks.pre]]
kind = "step"
id = "fetch-schema"
type = "shell"
command = "curl -fsS https://api.example.com/schema.json -o schema.json"
timeout_seconds = 60
retry_count = 3
retry_delay_seconds = 5

Examples

Validate the environment before drafting an issue

toml
[[commands]]
target = "issue"

[[commands.operations]]
name = "draft"

[[commands.operations.hooks.pre]]
kind = "check"
id = "git-installed"
type = "command_exists"
path = "git"
on_false = "fail"

[[commands.operations.hooks.pre]]
kind = "check"
id = "in-git-repo"
type = "command_success"
command = "git rev-parse --git-dir"
on_false = "fail"

[[commands.operations.hooks.pre]]
kind = "check"
id = "has-templates"
type = "directory_exists"
path = "templates"
on_false = "skip"

Verify output after a command

toml
[[commands]]
target = "docs"

[[commands.operations]]
name = "create"

[[commands.operations.hooks.post]]
kind = "check"
id = "readme-exists"
type = "file_exists"
path = "README.md"
on_false = "fail"

[[commands.operations.hooks.post]]
kind = "check"
id = "readme-has-title"
type = "file_contains"
path = "README.md"
content = "# "
on_false = "skip"

Hooks and workflow steps

Command hooksWorkflow steps
ScopeOne commandA multi-command workflow
Defined incommands.tomlA workflow definition file
RunsOn every invocation of the commandOnly in that workflow
Typical useValidation, setup, cleanupOrchestration

When a workflow runs a command, the order is: workflow pre-steps, command (with its hooks), workflow post-steps.

Debugging

Forge records hook execution in the job's transaction log, .forge/work/job_<id>/logs/transaction.jsonl:

bash
# All step executions
jq 'select(.event_type == "step_execution")' .forge/work/job_*/logs/transaction.jsonl

# Failed steps
jq 'select(.event_type == "step_execution" and .outcome == "failure")' .forge/work/job_*/logs/transaction.jsonl

# Check results
jq 'select(.event_type == "check_evaluation")' .forge/work/job_*/logs/transaction.jsonl

Step events include step_id, step_type (shell, builtin, or agent), phase, outcome (success, failure, or skipped), duration_ms, command, and error. Check events include check_type, result, and action. See Transaction Log Format.

Legacy syntax

Hook syntax other than hooks.pre and hooks.post is deprecated. Forge warns:

pre_items and post_items fields are deprecated. Use [[commands.operations.hooks.pre]] and [[commands.operations.hooks.post]] instead. The old syntax will be removed in a future version.

Operation-level pre_steps, pre_checks, post_steps, and post_checks are still accepted for backward compatibility. Use hooks.pre and hooks.post for new configuration.

When you extend a built-in operation, Forge runs the built-in hook items first and your items after them. If an operation defines both hooks.pre and the legacy syntax, hooks.pre takes precedence and Forge logs a warning. The same rule applies to hooks.post.

See Also