Working with hooks
Concepts live in Hooks; the full schema in the reference. This page is patterns and operations.
The essential pattern: env files per workspace
Section titled “The essential pattern: env files per workspace”hooks: post-create: install: command: "npm ci" condition: "file_exists:package.json" migrate: command: "npm run migrate" environment: DATABASE_URL: "{{ service['app-db'].url }}" post-switch: env: action: type: write-env path: .env.local vars: DATABASE_URL: "{{ service['app-db'].url }}" REDIS_URL: "redis://{{ service.cache.host }}:{{ service.cache.port }}/{{ service.cache.database }}"post-switch fires on every switch (including the implicit one after creation), so .env.local always matches the active workspace — in the right worktree.
The write-env action is preferred over echo … > .env.local: no shell, no quoting bugs, no approval prompt, and it merges instead of clobbering.
More patterns
Section titled “More patterns”hooks: post-start: dev-server: command: "npm run dev -- --port {{ workspace | hash_port }}" background: true # deterministic port per workspace pre-commit: test: { command: "npm test", continue_on_error: false } lint: { command: "npm run lint", continue_on_error: false } post-remove: cleanup: command: "docker stop {{ repo }}-{{ workspace | sanitize }}-app 2>/dev/null || true" continue_on_error: true post-create: announce: command: "printf '%s\\n' 'Workspace {{ workspace }} ready'"Conditions keep hooks polyglot-safe (condition: "file_exists:requirements.txt" for the Python path, package.json for Node) and context-aware (is_worktree, workspace_matches:^agent/.*, trigger_is:vcs). All conditions →
Recipes
Section titled “Recipes”Recipes are intent-based hook generators. Installing one probes your project — files on disk and tools actually installed (PATH + mise shims) — proposes concrete parameter values, and writes one lean set of plain hooks into .devflow.yml (never overwriting your entries). Generated hooks are ordinary hooks: edit them freely afterwards.
devflow hook recipes # list + what detection found for THIS projectdevflow hook setup # wizard: pick from detected recipes, install in one godevflow hook install env-file # interactive: confirm detected paramsdevflow hook install db-migrate --param command="sqlx migrate run" --yesdevflow hook install install-deps --yes # accept detected values (CI/agents)| Recipe | Intent | Detection |
|---|---|---|
env-file | write per-workspace service URLs into an env file (approval-free write-env, merge mode) | configured services → DATABASE_URL/REDIS_URL/…, existing .env* file |
patch-config | find-and-replace a value in any config file (repeatable, one hook per file) | none — fully parameterized |
db-migrate | run migrations after create/switch (one editable command) | prisma, Rails, Django, alembic, sqlx, diesel, dbmate |
install-deps | install dependencies with the package manager the project uses | lockfile and binary installed (bun > pnpm > yarn > npm, uv, cargo) |
workspace-setup | copy .env.example → .env.local, mise trust, direnv allow | only the parts that apply (file present + tool installed) |
multiplexer-session | auto-open a tmux/zellij session in the worktree after creation | tmux/zellij installed or execute configured |
Approvals in automation
Section titled “Approvals in automation”Interactive runs prompt once per hook template. For CI and agents (--non-interactive), unapproved hooks are skipped with a warning — the command still succeeds and reports per-phase counts. Make hooks actually run by either:
# approve specific templates once per project (covers all workspaces/worktrees)devflow hook approvals add "npm run migrate"devflow hook approvals list
# or blanket-approve config hooks for this runDEVFLOW_APPROVE_HOOKS=1 devflow --json --non-interactive switch -c agent/t42In JSON output, hooks[].skipped > 0 is your signal that an approval is missing. --no-verify is different — it skips all hooks entirely.
Running and debugging hooks
Section titled “Running and debugging hooks”devflow hook show # everything configureddevflow hook show post-createdevflow hook run post-create # run a phase manuallydevflow hook run post-create migrate # one named hookdevflow hook run post-create --workspace feature/authdevflow hook explain post-switch # phase docs + when it firesdevflow hook vars # live template contextdevflow hook render "{{ service['app-db'].url }}"devflow hook triggers # VCS event → phase mappingdevflow hook actions # built-in action listCustom phases
Section titled “Custom phases”Any unknown phase name is a custom phase — define it and run it on demand:
hooks: load-fixtures: seed: "psql {{ service['app-db'].url }} -f fixtures.sql"devflow hook run load-fixtures