Skip to content

.github/workflows/first-turn.yml (additions)

A loop that cannot be stopped is not an engine โ€” it is a runaway cart. Before your serpent grows teeth (golems, fix lanes, self-merges), it must learn the Wardens' first law: everything autonomous is OFF until a named switch turns it on, and it stops the instant that switch is unset. Today you build the gate itself โ€” and then you meet the failure that killed the real engine three mornings straight without producing a single log line.

The real-world skills: opt-in automation via repository variables, go/no-go gate jobs, least-privilege permissions, and the GitHub Actions rule that a caller job's permissions cap every job in a workflow it calls โ€” validated at trigger time, where no linter can save you.

๐Ÿงญ Campaign note: Level 0101 is the Factory โ€” CI/CD & DevOps โ€” exactly where a gate belongs. The campaign's chapters climb its difficulty ladder, not the level order.

๐Ÿ“– The Legend Behind This Quest

When the quest-perfection engine first shipped, its conductor never played a note: three scheduled mornings in a row ended in startup_failure โ€” zero jobs, zero logs, only a single sentence on the run's page. The fix was two lines. The lesson was permanent: some laws are enforced by the realm itself, at the gate, before any of your code runs โ€” and your loop must be designed so that "off" is a clean, silent, zero-cost state, not an error.

๐ŸŽฏ Quest Objectives

By the end of this quest you will:

  • Add a kill switch โ€” a LOOP_ENABLED repository variable your workflow checks before doing anything
  • Build a gate job โ€” one cheap job that outputs go=true/false; every other job needs it
  • Apply least privilege โ€” workflow-level permissions: contents: read, elevated only where written proof requires it
  • Trigger and read a startup_failure โ€” and learn why gh run view --log-failed returns nothing for it
  • Grant caller permissions to a called workflow โ€” the two-line fix for the whole class

๐Ÿ—บ๏ธ Quest Prerequisites

  • ๐Ÿ“‹ Chapter I's potion-book repo with a green First Turn run
  • ๐Ÿ“‹ 15 minutes of patience for GitHub's scheduler (or use workflow_dispatch levers)

๐Ÿง™โ€โ™‚๏ธ Chapter 1: The Kill Switch and the Gate Job

The gate pattern is one cheap job that decides go or no-go โ€” and a workflow whose every other job refuses to start without it. Add this to your First Turn workflow:

{% raw %}

# .github/workflows/first-turn.yml  (additions)
permissions:
  contents: read          # the workflow-wide floor: read-only

jobs:
  gate:
    runs-on: ubuntu-latest
    outputs:
      go: ${{ steps.check.outputs.go }}
    steps:
      - id: check
        env:
          ENABLED: ${{ vars.LOOP_ENABLED }}
        run: |
          set -euo pipefail
          go=false
          if [ "$ENABLED" = "true" ]; then
            go=true
          else
            echo "loop idle (LOOP_ENABLED=$ENABLED) โ€” flip the variable to arm."
          fi
          echo "go=$go" >> "$GITHUB_OUTPUT"

  turn:
    needs: gate
    if: ${{ needs.gate.outputs.go == 'true' }}
    permissions:
      contents: write     # elevated ONLY here โ€” this job commits the ledger
    runs-on: ubuntu-latest
    steps:
      # โ€ฆ your Chapter I steps unchanged โ€ฆ

Arm and disarm it like a warden, and watch both behaviors:

gh variable set LOOP_ENABLED --body true    # armed
gh workflow run first-turn.yml && gh run watch

gh variable delete LOOP_ENABLED             # disarmed
gh workflow run first-turn.yml && gh run watch   # gate: success, turn: skipped

The disarmed run is the important one: green, silent, zero side effects. An automation that errors when disabled trains humans to ignore red โ€” the Sentinel's Horn fatigue rule applies to CI too.

โš”๏ธ Skills You'll Forge

  • The vars.* context (repository variables) vs secrets.* โ€” switches are variables, credentials are secrets
  • Job outputs โ†’ needs.<job>.outputs.<name> โ€” how gate verdicts travel
  • Per-job permissions elevation over a read-only floor

๐Ÿ” Knowledge Check

  • Why check the switch in a job instead of skipping the whole workflow with a repository setting?
  • When you later add auth (Chapter III), the gate should require the switch and the secret. Why both?

๐Ÿง™โ€โ™‚๏ธ Chapter 2: The Error With No Logs โ€” startup_failure

Some failures happen before any job exists. GitHub validates the whole workflow graph at trigger time; if the graph itself is illegal, the run is stamped startup_failure โ€” no jobs, no logs, and gh run view --log-failed shrugs. The real engine hit exactly this, three days straight, with this shape:

{% raw %}

# The trap: a workflow-level read-only floorโ€ฆ
permissions:
  contents: read

jobs:
  fix:
    uses: ./.github/workflows/quest-fix.yml   # โ€ฆcalling a workflow whose jobs
    secrets: inherit                          # request contents:write โ€” DENIED

The realm's law: a caller job's permissions cap every job in the workflow it calls. The called workflow requested contents: write, pull-requests: write; the caller granted contents: read. Verdict, rendered at the gate, before the gate job itself could even run:

Invalid workflow file โ€ฆ
The nested job 'fix' is requesting 'contents: write, pull-requests: write',
but is only allowed 'contents: read, pull-requests: none'.

The two-line cure โ€” grant on the caller job exactly what the called jobs request:

{% raw %}

  fix:
    permissions:
      contents: write
      pull-requests: write
    uses: ./.github/workflows/quest-fix.yml
    secrets: inherit

Two survival notes from the same battle:

  • actionlint cannot catch this. The rule is validated server-side. Lint locally, but smoke-test the graph by dispatching with inputs that make the expensive jobs no-op (the real engine uses a max_slices: 0 dispatch input for exactly this).
  • The error lives only on the run's web page. When a scheduled run shows startup_failure with zero jobs, open it in the browser โ€” the one-sentence verdict is printed there and nowhere else.

๐Ÿ” Knowledge Check

  • Why does GitHub validate called-workflow permissions at trigger time instead of when the job starts?
  • Your gate job never ran during the startup failures. What does that tell you about where in the lifecycle graph validation sits?

๐Ÿ” Reproduce It

Study the real two-line fix and its battle report:

  • PR #422 โ€” bamr87/it-journey@cfeb5e3aa (+24/โˆ’2): the caller-permissions grant, the artifact-name fix, and the max_slices=0 smoke-test pattern, written up after three silent startup_failure mornings
  • The gate pattern at full scale: gate: jobs in .github/workflows/quest-perfection.yml, quest-fix.yml, and quest-walkthrough.yml โ€” every lane carries its own *_ENABLED switch (QUEST_PERFECTION_ENABLED, QUEST_FIX_ENABLED, โ€ฆ), so a human can kill one arm without silencing the others

๐ŸŽฎ Mastery Challenge

Objective: stage-gate your potion book like a real fleet.

Success Criteria: - [ ] Split your loop into two lanes โ€” first-turn.yml (check + ledger) and a stub potion-fix.yml reusable workflow with its own FIX_ENABLED gate โ€” and call the stub from the first lane - [ ] Deliberately reproduce the startup_failure (call the stub while its jobs request contents: write under a read-only caller), read the error on the run page, then fix it with the caller grant - [ ] Prove both kill switches work independently: walk-only mode (LOOP_ENABLED=true, FIX_ENABLED unset) runs the check and skips the fix lane cleanly

๐ŸŽ Rewards & Progression

  • ๐Ÿšฆ Warden of the Gate โ€” earned when your disarmed loop runs green, silent, and free
  • โšก Skills unlocked: gate jobs ยท kill switches ยท least privilege ยท startup-failure forensics
  • ๐Ÿ“Š +75 XP

๐Ÿ—บ๏ธ Quest Network

graph LR
    Prev[๐Ÿ” I ยท The First Turn] --> Cur[๐Ÿšฆ II ยท The Warden's Gate]
    Cur --> Next[๐Ÿค– III ยท Summon the Golem]
    click Prev "/quests/0000/ouroboros-loop-01-the-first-turn/"
    click Next "/quests/0011/ouroboros-loop-03-summon-the-golem/"

๐Ÿ”ฎ Next Adventures

๐Ÿ“š Resource Codex

๐Ÿ•ธ๏ธ Knowledge Graph

Structured wiki-links connect this quest to the IT-Journey knowledge graph.

Campaign hub: [[Epic Quest: The Ouroboros Loop]] Previous: [[The First Turn]] ยท Next: [[Summon the Golem]] Level home: [[Level 0101 - CI/CD & DevOps]]