Skip to content

Hooks reference

Concepts and patterns: Hooks concept · hooks guide.

PhaseFiresBlocking
pre-switchbefore switching to a workspaceyes
post-createafter a new worktree/jj workspace was createdyes
post-startafter starting a stopped service workspaceno
post-switchafter every switch (including the one implied by creation)no
pre-removebefore removing a workspaceyes
post-removeafter removalno
pre-commitbefore a commit (installed Git pre-commit hook)yes
pre-service-createbefore creating service workspacesyes
post-service-createafter creating service workspacesno
pre-service-deletebefore deleting service workspacesyes
post-service-deleteafter deleting service workspacesno
post-service-switchafter switching service workspacesno
anything elsecustom phase — run with devflow hook run <phase>no

A failing hook in a blocking phase aborts the operation (unless continue_on_error: true). Non-blocking phases are best-effort: failures are reported in the result summary but don’t abort. background: true hooks are spawned concurrently and awaited at process exit up to DEVFLOW_BACKGROUND_HOOK_TIMEOUT seconds (default 30).

hooks:
<phase>:
<name>: "<command>" # Simple
<name>: # Extended
command: "<template>"
working_dir: "<rel-path>" # relative to the workspace's worktree (or project root)
condition: "<condition>"
continue_on_error: false
background: false
environment: { KEY: "<template>" }
<name>: # Action (shell-free, no approval needed)
action:
type: <action-type>
…action fields…
condition: "<condition>"

Hooks run with their working directory set to the target materialized workspace.

Rendered with MiniJinja (Jinja2-compatible) in commands, environment values, action fields, and conditions.

VariableDescriptionExample
{{ workspace }}raw VCS workspace/ref namefeature/auth
{{ workspace_key }}backend service/path key (collision-safe for new workspaces)feature_auth_fc659bd73585
{{ workspace_sanitized }}compatibility alias for workspace_keyfeature_auth_fc659bd73585
{{ name }}project name (config name: or directory)my-project
{{ repo }}repository directory namemy-project
{{ worktree_path }}absolute worktree path, when in worktree context/…/my-project.feature_auth_fc659bd73585
{{ default_workspace }}configured default workspacemain
{{ commit }} / {{ short_commit }}HEAD SHA / abbreviateda1b2c3d… / a1b2c3d
{{ base }}base/parent workspace (creation hooks)main
{{ trigger_source }}what invoked the hook: cli, vcs, guivcs
{{ vcs_event }}originating VCS event, when anypost-checkout
{{ service.<name>.host }}service hostlocalhost
{{ service.<name>.port }}service port55433
{{ service.<name>.database }}database/bucket/indexfeature_auth_fc659bd73585
{{ service.<name>.user }} / .passwordcredentialspostgres
{{ service.<name>.url }}full connection URLpostgresql://…

Use bracket access for hyphenated service names: {{ service['app-db'].url }}. Inspect live values with devflow hook vars.

Shell hook commands also run with these variables exported, so external scripts (where template syntax is unavailable) can read the workspace identity directly:

VariableContents
DEVFLOW_WORKSPACEraw VCS workspace name (same as {{ workspace }})
DEVFLOW_WORKSPACE_KEYbackend service/path key (same as {{ workspace_key }})
DEVFLOW_BRANCHcompatibility alias for the raw name

devflow switch -x/--open sessions and GUI terminals export the same variables.

FilterDescriptionExample
sanitizereplace / and \ with -{{ workspace | sanitize }}feature-auth
sanitize_dbdatabase-safe identifier (≤63 chars, hash suffix){{ workspace | sanitize_db }}feature_auth
hash_portdeterministic port in 10000–19999{{ workspace | hash_port }}14523
lower / uppercase mapping (MiniJinja built-in){{ workspace | upper }}
replacestring replacement (MiniJinja built-in){{ workspace | replace("/", "-") }}
truncatefirst N characters{{ workspace | truncate(20) }}

Conditions are template-rendered first, then evaluated. Built-ins:

ConditionTrue when
file_exists:<path> / dir_exists:<path>path exists (relative to the hook working dir; comma-separated alternatives = any)
command_exists:<bin>binary found on PATH or in mise shims (comma-separated alternatives = any)
workspace_is:<name> / workspace_not:<name>workspace equals / differs
workspace_matches:<regex>workspace matches the regex
is_default_workspace / not_default_workspaceworkspace is / isn’t the configured default workspace
is_worktree / not_worktreea worktree exists for this context
trigger_is:<src> / trigger_not:<src>trigger source is / isn’t cli·vcs·gui
env_set:<VAR> / env_is:<VAR>=<value>environment variable set / equals
always · true / never · falseconstant
anything elseexecuted as a shell command; exit 0 = true
TypePurposeKey fields
write-envcreate/merge an env filepath, vars: {K: V}
write-filewrite a file from a templatepath, content
copycopy a file/dirfrom, to, overwrite
replacein-file string/regex replacementfile, pattern, replacement
shellrun a command (same as command:)command
docker-execexec inside a containercontainer, command, user
httpHTTP request (webhooks, health checks)url, method, body

devflow hook actions lists the authoritative set with all fields. Actions are shell-free and skip the approval system.

Terminal window
devflow hook recipes # list + per-project detection (files, installed tools)
devflow hook setup # wizard: multi-select detected recipes, install in one go
devflow hook install <name> # interactive params (detected values prefilled)
devflow hook install <name> --param k=v --yes # non-interactive; never overwrites your entries

env-file · patch-config · db-migrate · install-deps · workspace-setup · multiplexer-session — descriptions in the hooks guide. (docker-compose was removed in favor of process daemons; local-dev-setup is now workspace-setup.)

Installed Git hooks dispatch to devflow phases:

Git hookdevflow phases
post-checkoutpost-switch (plus post-create when the workspace is new)
pre-commitpre-commit

devflow hook triggers displays the active mapping.

Shell hooks from the committed config require one-time approval per user; built-in actions don’t. Approvals are keyed by canonical project root + command template in ~/.config/devflow/hook_approvals.yml — one approval covers all workspaces and worktrees. Non-interactive behavior and pre-approval: hooks concept → approvals.