メインコンテンツまでスキップ

Lifecycle hooks

Lifecycle hooks are the setup scripts of a template. Each hook is a Bash script that runs at one moment in a session's life:

HookWhen it runsUsage
On createOnce, when the session is createdClone a repository, install dependencies, build
On claimWhen the session is assigned to someoneSet up that person's own tokens and tools
On restoreEvery time a stopped session is restoredRestart services or a simulator

Every hook is optional, so add only the ones your template needs. A template without hooks gives you a session you can use as soon as its machine boots.

The same template works for sessions you create directly and for sessions claimed from a warm pool. Lifecycle hooks replace the warmup and startup scripts of older templates. See Converting a template with warmup and startup scripts.

When each hook runsClick to copy link​

Whether you create a session directly or claim it from a warm pool, the hooks run in the same order. The difference is when:

MomentDirect sessionSession from a warm pool
CreationOn createOn create, in the pool, before anyone asks for the session
Assigned to someoneSystem setup, then On claim, right after On createSystem setup, then On claim, when you claim it
RestoreOn restoreOn restore

System setup is Bitrise's own step, not a hook. It sets up the owner's SSH key and Claude credentials, if they supplied them. For a session claimed from a warm pool, if the owner turned on the GitHub integration, it also signs them in to GitHub, so git and gh work as them and their commits carry their GitHub identity. A direct session gets its GitHub sign-in when the machine boots instead. System setup runs only when a person gets the session: a workspace-owned session skips it.

GitHub access for hooks depends on the session type. It applies only if the owner turned on the GitHub integration:

  • On claim can use the owner's GitHub access. On a direct session, the integration is set up when the machine boots. On a session claimed from a warm pool, System setup signs the owner in before On claim runs.
  • On create can use the owner's GitHub access on a direct session only.
  • On create in a warm pool needs a token of its own, because the session has no owner while On create runs.

A few rules hold for every session:

  • Hooks run one at a time. On claim waits for On create, and if On create or System setup fails, On claim doesn't run.
  • On create and On claim run once per machine. A restore runs only On restore.
  • An added hook runs even if its script is empty. It finishes at once.

Because On claim also runs right after On create on a direct session, a template written for warm pools works without a pool too.

The template editor shows the same timeline above the hooks. Created from the template shows a direct session:

The timeline of a session created directly from a template: Session created, On create, Assigned to you, On claim, Ready, Stopped, On restore, Ready again

Claimed from a warm pool shows a session from a pool. The pool runs On create while the session is still shared, and the rest runs once it's yours:

The timeline of a session claimed from a warm pool: the pool boots a machine and runs On create, then the session waits until you claim it and On claim runs

Writing a hookClick to copy link​

A hook runs as the session user, in Bash, and starts in the home directory. It receives the variables and inputs of its scope as environment variables.

A hook is done when its script exits. Exit code 0 means it succeeded, any other exit code means it failed. Bitrise doesn't wait for anything else, so start long-running work, like a dev server, in the background and let the script exit:

cd ~/example-app
nohup npm run dev > ~/dev-server.log 2>&1 &

A background process keeps the environment it started with, including secrets. If the process doesn't need a secret, remove it with env -u:

env -u REPO_TOKEN nohup npm run dev > ~/dev-server.log 2>&1 &

This matters most in a warm pool: whoever claims the session can read the environment of a process that On create left running.

Keep these in mind:

  • Nobody can connect while On create runs. The terminal, SSH, remote access, and file transfers open when On create finishes. You can read its log in the meantime. If On create is still running after 2 hours, Bitrise ends it and marks it failed with exit code 124, so start long-running work in the background.
  • A stop keeps the disk, not the processes. Cloned repositories and installed tools survive a restore. Running services, simulators, and files in /tmp don't, so restart them in On restore.
  • Hooks don't wait for a virtual device. On a device session, the simulator or emulator boots alongside the hooks, so it may not be ready yet when On create or On restore runs. A script or AI agent that uses the device waits for both the setup and the device to be ready. bitrise-cli rde session create --wait does this for you.

Variables and inputsClick to copy link​

A template carries two kinds of values:

  • Variables are stored on the template and are the same for every session. For example, a repository URL.
  • Inputs are filled in for each session. For example, a branch to check out, or a personal access token.

Every variable and input belongs to one scope, and the scope decides which hooks receive it:

ScopeReceived by
SharedEvery hook
On createOn create only
On claimOn claim only
On restoreOn restore only

A hook receives the Shared values plus the values of its own scope. If a hook's own value has the same key as a Shared one, the hook's value wins. The same key can exist in several scopes, and one value or saved input then fills all of them.

Who fills in the inputs depends on how the session starts:

  • A direct session: whoever creates it fills in every input, in every scope.
  • A warm pool: the Shared and On create inputs are set once, on the pool. Whoever claims a session fills in the On claim and On restore inputs, typed in or from their saved inputs. A session kept as a shared (Org-owned) session accepts typed plain values only.

Nobody is asked for anything at restore: On restore inputs are filled in up front.

Secret and protected variablesClick to copy link​

Every variable has a visibility:

VisibilityWhat it means
PlainReadable by anyone who can see the template
SecretEncrypted and masked; members with access can reveal it
ProtectedSecret and write-only: you can replace it, but never see it again

Also in terminalClick to copy link​

Hooks always receive their values. Switch on Also in terminal to also write a value to the session's shell profile, so your terminal and SSH sessions see it too. Shared values are written when the session is created, and a hook's own values when that hook runs. If two of them have the same key, the one written last wins. Protected values can't be written to the terminal.

Values in a warm poolClick to copy link​

On claim and On restore receive the Shared values, so whoever claims a session from a pool gets the Shared values too. Keep a value that only the pool should use, like a token to clone the repository, in On create instead.

The page of a claimed session doesn't show the values of its On create inputs: it lists them as set on the pool. But a Shared or On create value with Also in terminal switched on stays in the machine's shell profile, where the claimer can read it.

Using hooks with a warm poolClick to copy link​

A warm pool runs only On create. A warm session is ready to claim once On create succeeds, and everything else runs after the claim. To get the most out of a pool:

  • Put the slow setup in On create: cloning, installing, building.
  • Give On create shared credentials, like a read-only bot token. Saved inputs are personal, so a pool can't use them.
  • Put personal setup in On claim, with On claim inputs that whoever claims fills in.
  • Don't ask for a GitHub token in On claim. If whoever claims has the GitHub integration turned on, System setup signs them in to GitHub before On claim runs.

A template without On create gives the pool nothing to prepare, so its sessions are ready to claim as soon as they boot. The pool form warns you when the template has no On create, and notes when it has no On claim. Both messages link to the template editor.

To serve device preview links from a pool, the template can't have required On claim or On restore inputs, because nobody is there to fill them in. Give those inputs a default value instead.

Example: an iOS app templateClick to copy link​

This template prepares an iOS app repository. It works for direct sessions and for a warm pool alike.

On create clones the repository with a read-only bot token and installs the dependencies. The token is a secret On create input, so it never reaches On claim, On restore, or whoever claims the session:

git clone "https://x-access-token:${REPO_TOKEN}@github.com/example-org/example-app.git" ~/example-app
cd ~/example-app
# Keep the token out of the clone's configuration.
git remote set-url origin https://github.com/example-org/example-app.git
bundle install
bundle exec pod install

On claim sets the Git identity of whoever gets the session, from the On claim inputs GIT_NAME and GIT_EMAIL:

cd ~/example-app
git config user.name "$GIT_NAME"
git config user.email "$GIT_EMAIL"

A team that uses the GitHub integration can leave this hook out: System setup already sets the commit identity of whoever gets the session.

On restore starts the app's mock server again, because a stop ends every running process:

cd ~/example-app
nohup ./scripts/start-mock-server.sh > ~/mock-server.log 2>&1 &

The values:

KeyKindScope
APP_ENVVariable, plain, Also in terminalShared
REPO_TOKENInput, secretOn create
GIT_NAMEInput, requiredOn claim
GIT_EMAILInput, requiredOn claim

APP_ENV is Shared, so every hook receives it. With Also in terminal switched on, your terminal sees it too.

The On claim hook in the template editor, with its script and the GIT_NAME and GIT_EMAIL inputs

Following a session's setupClick to copy link​

The Lifecycle card on the session's page shows where the setup is, every hook run with its result, and each hook's log:

The Lifecycle card of a session, with System setup, On create, and On claim succeeded

The card's label tells you what the session is doing:

LabelWhat it means
Setting upOn create is running. You can connect once it finishes.
PersonalizingSystem setup and On claim are running. You can already connect.
RestoringOn restore is running after a restore.
ReadyEvery hook for this moment has finished.
Setup failedOn create failed.
Personalization failedSystem setup or On claim failed.
Restore script failedOn restore failed.

On a warm pool's page, a session that hasn't been claimed yet shows Warming up while On create runs, and Ready to claim when it's done.

A session is Running as soon as its machine is up, which is before On create finishes. If a script or an AI agent drives the session, wait for Ready instead. bitrise-cli rde session create --wait does this for you.

Each hook has its own log, and so does System setup. Logs are kept across restores, so you can still read how On create ran after the session has been stopped and restored.

When a hook failsClick to copy link​

A failed hook doesn't break the session. It stays usable, and its log shows what went wrong. Bitrise stops any process the failed hook left running.

  • On claim or On restore failed: click Run On claim again or Run On restore again on the Lifecycle card. The hook runs again on the same machine, and everything on the machine stays as it is.
  • System setup failed: click Retry system setup. On claim was skipped behind it, so when System setup succeeds, click Run On claim to finish the setup.
  • On create failed: click Rebuild. Rebuild discards the machine and its disk, and boots a fresh one for the same session. On create runs again from the start, followed by System setup and On claim. On create can't run again on the same machine.

The Lifecycle card of a session where On claim failed, with the Run On claim again button

A fixed template doesn't fix existing sessions

Running a hook again and Rebuild both repeat the script the session was created with. If the hook failed because of a mistake in the script, fix the template, then create a new session to use the fix.

When On create fails in a warm poolClick to copy link​

If On create fails, the pool replaces the machine and tries again. After three failures in a row, the pool shows Setup failed and stops warming:

  • Nobody can claim from the pool until you fix it.
  • The pool keeps the last failed machine as a session you can open, read the log of, and connect to. It stops automatically after four hours.
  • Once you fix the cause, click Retry on the pool's page. Retry deletes the failed machine and the pool starts warming again. Changing the template or the pool's configuration also starts it again.

Converting a template with warmup and startup scriptsClick to copy link​

New templates always use lifecycle hooks. Templates created before lifecycle hooks have a warmup script and a startup script instead, and they keep working without any change. Bitrise runs them as hooks:

  • On create runs the warmup script, then starts the startup script in the background.
  • On restore starts the startup script in the background.
  • Every variable and input is Shared.

To edit such a template with hooks, convert it:

  1. Open the template and click Edit template.
  2. In the Scripts section, click Convert to lifecycle hooks. The editor shows the hooks that the template runs today. If the warmup or startup script has unsaved changes, save them first: the button stays disabled until you do.
  3. Review the hooks, move values to a hook's scope if only that hook needs them, and click Save changes.

The Scripts section of a template with warmup and startup scripts, with the Convert to lifecycle hooks button

Converting is permanent

Once saved, a template can't go back to warmup and startup scripts. Sessions already created from it keep running as they were created.

Managing hooks from the CLI, an AI agent, or the APIClick to copy link​

Hooks go in the template's spec file, as the hooks object. The hook key of a variable or input sets its scope; leave it out for Shared:

{
"name": "iOS app",
"stack_id": "STACK_ID",
"machine_type": "MACHINE_TYPE",
"hooks": {
"on_create": "git clone https://github.com/example-org/example-app.git ~/example-app",
"on_claim": "cd ~/example-app && git config user.email \"$GIT_EMAIL\"",
"on_restore": ""
},
"session_inputs": [
{ "key": "GIT_EMAIL", "required": true, "hook": "on_claim" }
]
}
bitrise-cli rde template create --file template.json

A hook in the hooks object runs, even with an empty script like on_restore above. Leave a hook out to not have it.

To follow and fix a session's setup:

CommandWhat it does
bitrise-cli rde session view SESSION_IDShow the session's setup phase and every hook run.
bitrise-cli rde session logs SESSION_ID --stage on-createPrint a hook's log. The stages are on-create, on-claim, on-restore, and personalize for System setup. Add --attempt 2 to see only the second run of a hook.
bitrise-cli rde session run-hook SESSION_ID --hook on_claimRun On claim, On restore, or System setup (personalize) again.
bitrise-cli rde session rebuild SESSION_IDDiscard the machine and run the setup again from On create.
bitrise-cli rde warm-pool retry WARM_POOL_IDStart a pool warming again after On create failed three times in a row.

When you claim from a warm pool, --input sets the On claim and On restore inputs:

bitrise-cli rde session create my-fix --warm-pool ios-app --input GIT_EMAIL=[email protected]