How an edit triggers a redeploy
Business view
You don't need to remember to "redeploy" after changing the Sandbox settings โ it happens automatically. Open a PR, and it deploys. Change the YAML block afterward (say, you realize you need whatsapp: true), and it redeploys with the new settings. Change something else in the description โ fix a typo, add more context, answer a reviewer's question โ and nothing redeploys; the existing sandbox just keeps running. The system is deliberately lazy: it only pays the ~10-30 minute deploy cost when the actual settings changed.
Technical view
This logic already existed in .github/workflows/sandbox.yml before this PR โ the template change doesn't touch it, but understanding it is what makes the block in the template meaningful rather than decorative.
pull_request triggers
sandbox.yml:19-24 fires on opened, synchronize, reopened, edited, closed. synchronize covers ordinary new commits; edited is the one that matters for the settings block, since it fires whenever the PR title or body changes.
The gate job decides whether to deploy
sandbox.yml:44-78, job gate, step Decide (id: decide):
if [ "$ACTION" != "edited" ]; then
echo "deploy=true" # opened/synchronize/reopened
elif [ "$BODY_CHANGED" != "true" ]; then
echo "deploy=false" # title-only edit
elif [ "$(extract "$NEW_BODY")" != "$(extract "$OLD_BODY")" ]; then
echo "deploy=true" # sandbox block changed
else
echo "deploy=false" # body edit, block unchanged
fi
(sandbox.yml:69-77)
opened, synchronize, and reopened always deploy. For edited, it only deploys if the description text actually changed (BODY_CHANGED, sourced from github.event.changes.body != null, sandbox.yml:55) and the extracted Sandbox block differs before vs. after (sandbox.yml:73). Editing unrelated text in the description โ or retitling the PR โ does not trigger a redeploy.
The extract() helper (sandbox.yml:60-68) is an awk state machine: find a ##-level heading whose text contains "sandbox" (case-insensitive), then capture everything between the next fenced-code-block markers. It's duplicated (not shared) in the deploy job's parse step (sandbox.yml:97-103) โ same logic, run twice because the gate job only has access to old/new PR body text, while the deploy job needs the parsed block from the current body.
The deploy job parses and applies it
sandbox.yml:80-138, gated by needs.gate.outputs.deploy == 'true' (sandbox.yml:82):
Parse sandbox settings from PR description(sandbox.yml:86-110)
re-extracts the block from github.event.pull_request.body, then:
- if the block is non-empty and
yqis installed, converts YAML โ JSON
into $RUNNER_TEMP/sandbox-settings.json (sandbox.yml:104-105);
- otherwise writes
{}(sandbox.yml:107) โ no block means all
defaults.
- The parsed JSON is echoed into the job log inside a collapsible
::group::๐งช parsed sandbox settings (sandbox.yml:110), useful for debugging a malformed block.
Deploy sandbox for <branch>(sandbox.yml:112-138) runs
/opt/homelab/sandbox/bin/sandbox dentolize <branch> deploy, reading SANDBOX_SETTINGS_FILE from the environment (inherited from step 1's $GITHUB_ENV write, sandbox.yml:109). The actual interpretation of two_regions / whatsapp / cron / seed / env happens inside that CLI binary, which lives on the self-hosted runner outside this repository โ not verifiable from packages/ source.
PR_BODYis passed viaenv:, never string-interpolated into the shell
script (sandbox.yml:88-90 comment: "avoid script injection from the body") โ a PR author fully controls the body text, so treating it as trusted shell input would be a command-injection vector.
The settings summary line
SETTINGS_SUMMARY itself is produced by sandbox โฆ info (sandbox.yml:147), i.e. by the same external CLI, and surfaces in two places:
- The job summary table, always:
| **Settings** | $SETTINGS_SUMMARY |
(sandbox.yml:211).
- The PR comment, only conditionally
(sandbox.yml:292): settingsSum && settingsSum !== "defaults (no settings block)". So a PR that never touches the Sandbox block gets a comment with no _settings: โฆ_ line at all; only a PR with at least one non-default value shows it. This is the mechanism behind the settings: two_regions=false whatsapp=false cron=true env_overrides=0 line shown in the Walkthrough โ reproduced there since a PR using exactly the template's own defaults, like this one's sandbox, would in practice not show that line at all (a subtlety worth knowing if you go looking for it on a default-settings PR and don't find it).
Docs regeneration is a separate, one-shot step
sandbox.yml:149-157 kicks off sandbox-docs โฆ generate --detach โ but only if: github.event.action == 'opened'. Editing the Sandbox block on an already-open PR redeploys the sandbox but does not regenerate this docs site; that's a manual "Sandbox Ops โ update-docs" action (referenced in the PR comment text at sandbox.yml:300).