Skip to content

Add lifecycle hooks ​

Run a repository command or an addon's listener at a worktree's lifecycle points, including a freeze.

You need: a committed .bakers/conductor.config.js, the standard herdr executor and a command that is safe to run repeatedly.

  1. Add a hooks object to .bakers/conductor.config.js. Each value is one shell command string. This complete file adds five hooks to an existing config:

    js
    export default {
      bakers: { project: 'demo' },
      conductor_id: 'alex-alex-laptop-demo',
      addons: ['@bakers-build/addon-remote-drupal', '@bakers-build/addon-herdr'],
      hooks: {
        after_create: 'pnpm install --frozen-lockfile',
        before_run: 'git status --short',
        after_done: 'bakers ports down mailpit',
        on_freeze: 'ddev stop',
        on_resume: 'ddev start',
      },
    };

    Keep existing values; script longer commands. bakers config init --scope project can write hooks from --after-create '<command>' and --after-done '<command>'.

  2. Make every command repeatable. It runs through sh -c in the ticket's worktree with your permissions, so its tool must be on the conductor's PATH.

  3. Verify:

    sh
    bakers config check --scope project

    It proves only that the file is valid, so also run the command twice in a disposable worktree and watch one dispatch.

Done when: a new worktree shows the effect of after_create and the log shows no lifecycle hook failed line.

Ask your AI

"Read this repository's conductor config and add an after_create hook using the project's existing install command."

When each hook runs ​

HookRunsUse for
after_createEvery dispatch, after the worktree exists, including a reused worktreeConvergent dependency setup
before_runAfter after_create (and a fresh worktree's opening layout), before the agent startsQuick refresh, launch env
after_runAfter an ended run's worker stopped, before the run is closed; at once when a launch failsStopping a run's sidecar
after_doneDuring cleanup of a finished ticket, before the worktree is removedReleasing local resources
on_freezeOnce when a poll sees the ticket frozen, in its worktree on this machineStopping local services
on_resumeOnce when a poll sees it unfrozenRestarting them

Runs resume and a failed close write repeats after_run, so tolerate repetition. If the worktree directory is gone, after_run and after_done run nothing.

Environment ​

after_create, before_run, on_freeze and on_resume inherit the conductor's environment, these values and the ticket's environment variables:

VariableValue
BB_URLServer URL of the selected connection
BB_IDTicket UUID
BB_TICKET_IDENTIFIERTicket identifier
BB_STATERun's start state; current state for freeze hooks
BB_RUN_UUIDCurrent run UUID; unset for freeze hooks
BB_TUNNEL1 if the machine allows preview tunnels, else 0
WORKSPACE_ROOTCheckout root; the working directory is the ticket worktree
TICKET_URLOrigin issue URL, when there is one
BB_PROJECT, BB_PROJECT_IDProject name and UUID, when known
BB_WORKSPACE, BB_WORKSPACE_IDTicket workspace name and UUID, when known
BB_WORKSPACE_IDSComma-separated reachable workspace UUIDs

Optional values are omitted rather than empty. The after_run command gets them rebuilt from the current ticket; the after_done command gets none of them.

Failure behavior ​

Hooks are best effort: a failing command or listener is logged and dispatch or cleanup continues. A required gate belongs in the WORKFLOW.md checks. A top-level hooks object replaces the herdr addon's with.hooks as a whole.

Add a listener from an addon ​

A conductor addon's preset can export a hooks accumulator; each listener defines any of these slots:

ts
import type { Preset } from '@bakers-build/conductor/contract';

export const hooks: Preset['hooks'] = (acc) => [
  ...acc,
  {
    id: 'demo-sidecar',
    async before_run(ctx) {
      ctx.env.DEMO_SIDECAR_URL = 'http://127.0.0.1:4599';
    },
    async after_run(ctx) {
      // stop what before_run started for ctx.run?.uuid
    },
  },
];

Listeners run awaited in addons order, children first, before the slot's project command. ctx carries worktreePath, ticket, state, env and run. At dispatch a key added to env reaches later listeners, the command and the agent; reserved keys (BB_*, WORKSPACE_ROOT, TICKET_URL) and invalid entries are restored. Never log env values.

At after_run and after_done, ctx is rebuilt from the records, also after a restart: find what you started by ctx.run.uuid.

Clean up a preview ​

after_done: 'bakers ports down mailpit' stops a tunnel opened with bakers ports up mailpit. See Ports.