From e95d29ab73b906d2b44262211bcd070d0e71059c Mon Sep 17 00:00:00 2001 From: Eric Fitzgerald Date: Thu, 18 Jun 2026 22:19:52 +0700 Subject: [PATCH 1/7] docs(claude): note docs/superpowers exception to the no-docs/ rule Superpowers skills (brainstorming, writing-plans) write specs and plans under docs/superpowers/; document that this subtree is exempt from the 'do not update docs/' rule (wiki-only) for tool-generated artifacts. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01Q6YFSYL252h71BH5ZL8QqT --- CLAUDE.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CLAUDE.md b/CLAUDE.md index 81b4ede7..20ce6497 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -500,10 +500,11 @@ When working with JSON files **larger than 100KB**, use streaming approaches wit **IMPORTANT**: All project documentation is maintained in the GitHub Wiki. Do NOT update markdown files in the `docs/` directory - they are deprecated and will be removed. -Do not update or add any content to the docs/ directory. Instead, update or add the content to the appropriate page on the tmi wiki. +Do not update or add any content to the docs/ directory (except the `docs/superpowers/` subtree — see exception below). Instead, update or add the content to the appropriate page on the tmi wiki. - **Authoritative documentation**: GitHub Wiki (https://github.com/ericfitz/tmi/wiki) - **Local `docs/` directory**: Deprecated, do not update +- **Exception — `docs/superpowers/`**: Superpowers skills (brainstorming, writing-plans, etc.) legitimately write specs and plans under `docs/superpowers/` (e.g., `docs/superpowers/specs/`). This subtree is allowed and is NOT covered by the "do not update docs/" rule. It is only for superpowers-generated artifacts — do not put hand-authored project documentation here (that still belongs in the wiki). ## Python Development From d24ab8a21d93966e873074fbec9ea412d0e03aa1 Mon Sep 17 00:00:00 2001 From: Eric Fitzgerald Date: Thu, 18 Jun 2026 22:21:06 +0700 Subject: [PATCH 2/7] chore(repo): track docs/superpowers/ artifacts; add deps-bump spec & plan Un-ignore docs/superpowers/** so superpowers specs/plans are versioned again, and add the dependency-bump automation design + implementation plan. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01Q6YFSYL252h71BH5ZL8QqT --- .gitignore | 1 - .../plans/2026-06-18-deps-bump-automation.md | 584 ++++++++++++++++++ .../2026-06-18-deps-bump-automation-design.md | 126 ++++ 3 files changed, 710 insertions(+), 1 deletion(-) create mode 100644 docs/superpowers/plans/2026-06-18-deps-bump-automation.md create mode 100644 docs/superpowers/specs/2026-06-18-deps-bump-automation-design.md diff --git a/.gitignore b/.gitignore index 6bff6f92..3ff953e4 100644 --- a/.gitignore +++ b/.gitignore @@ -125,7 +125,6 @@ scripts/oci-env.sh # OCI public deployment secrets scripts/setup-oci-public.env -docs/superpowers/** test/integration/workflows.test test/configs/google-drive-test-docs.json test/configs/google-drive-credentials.json diff --git a/docs/superpowers/plans/2026-06-18-deps-bump-automation.md b/docs/superpowers/plans/2026-06-18-deps-bump-automation.md new file mode 100644 index 00000000..5610b784 --- /dev/null +++ b/docs/superpowers/plans/2026-06-18-deps-bump-automation.md @@ -0,0 +1,584 @@ +# Dependency Bump Automation — Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Run the existing `deps:bump` Claude Code skill automatically on GitHub Actions — on a weekly schedule, on a daily Dependabot-alert poll, and on manual dispatch — producing one independent PR per target branch (main + `dev/*`), with no cross-branch cherry-pick or merge. + +**Architecture:** A single workflow `.github/workflows/deps-bump.yml`. A `discover` job computes the target-branch matrix from the trigger type. A `bump` job (matrix, `fail-fast: false`) checks out each branch, sets up the toolchain, vendors the `bump` skill into the runner, runs `claude -p` headless to apply safe updates + commit, then opens a PR targeting that branch. Build is de-risked by a spike (Task 1) before scheduling/matrix are added. + +**Tech Stack:** GitHub Actions, Claude Code CLI (`@anthropic-ai/claude-code`), `gh` CLI, Go toolchain (`make build-server`/`make test-unit`/`make lint`), uv, Node/pnpm, `actionlint` for static workflow validation. + +## Global Constraints + +- Validation commands come from tmi's CLAUDE.md / Makefile: `make build-server`, `make test-unit` (fast, **no external deps**), `make lint`. The skill discovers these automatically (CLAUDE.md → Makefile precedence). +- Bump exclusions are honored by the skill from `## Bump Exclusions` in tmi CLAUDE.md (currently `github.com/golang/protobuf`), `.bump-config.json`, and `// pinned:` comments. Do not re-implement exclusion logic. +- Output model: **PR per branch** to a working branch named `deps/auto-bump//`; never push to the source branch directly. +- Branch set for "all": `main` + every `dev/*` branch. **No cherry-pick / merge between branches.** +- Claude auth: subscription via `CLAUDE_CODE_OAUTH_TOKEN` secret (generated by `claude setup-token`). Never use an API key. +- Skill source of truth: marketplace repo `ericfitz/skills`, path `deps/skills/bump/SKILL.md`. Vendor at runtime (shallow clone), do **not** commit a copy into tmi. +- Headless permission: run `claude -p` with `--dangerously-skip-permissions` (autonomous CI; no human to approve). **VERIFY exact flag in Task 1** — fallback is `.claude/settings.json` `permissions.defaultMode: bypassPermissions`. +- Workflow permissions: `contents: write`, `pull-requests: write`. Dependabot-alerts read may require a fine-grained PAT secret `DEPS_ALERTS_TOKEN` (verified in Task 5). +- Spec: `docs/superpowers/specs/2026-06-18-deps-bump-automation-design.md`. + +--- + +## Task 0: Prerequisites (one-time setup, mostly human actions) + +**Files:** none (repo/secret configuration) + +**Interfaces:** +- Produces: repo secret `CLAUDE_CODE_OAUTH_TOKEN`; `actionlint` available locally. + +- [ ] **Step 1: Generate the Claude OAuth token (human runs locally)** + +```bash +claude setup-token +# Completes OAuth in browser; prints a ~1-year token to stdout. Copy it. +``` +Expected: a token string printed (not saved to disk). + +- [ ] **Step 2: Add the token as a repo secret** + +```bash +gh secret set CLAUDE_CODE_OAUTH_TOKEN --repo ericfitz/tmi +# Paste the token when prompted. +gh secret list --repo ericfitz/tmi | grep CLAUDE_CODE_OAUTH_TOKEN +``` +Expected: `CLAUDE_CODE_OAUTH_TOKEN` appears in the list. + +- [ ] **Step 3: Install actionlint locally (for static validation in later tasks)** + +```bash +go install github.com/rhysd/actionlint/cmd/actionlint@latest +actionlint -version +``` +Expected: a version string prints. + +- [ ] **Step 4: Confirm the skill path exists upstream** + +```bash +gh api repos/ericfitz/skills/contents/deps/skills/bump/SKILL.md --jq '.path' +``` +Expected: `deps/skills/bump/SKILL.md`. + +No commit (configuration only). + +--- + +## Task 1: Spike — headless bump on one branch (de-risk) + +Prove the headless skill run works end-to-end on `dev/1.4.0` with a minimal, manually-dispatched workflow. This resolves the flagged unknowns (exact permission flag, OAuth in CI, toolchain completeness, skill discovery) before any scheduling/matrix work. + +**Files:** +- Create: `.github/workflows/deps-bump.yml` (minimal, single-job, dispatch-only — grown in later tasks) + +**Interfaces:** +- Produces: a verified `claude -p` invocation and the exact toolchain setup steps that later tasks reuse. + +- [ ] **Step 1: Create the minimal spike workflow** + +```yaml +# .github/workflows/deps-bump.yml +name: Dependency Bump (Claude) + +on: + workflow_dispatch: + inputs: + branch: + description: 'Branch to bump' + default: 'dev/1.4.0' + type: string + +permissions: + contents: read # spike only reads + commits locally; no push yet + +jobs: + bump: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.inputs.branch }} + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: go.mod + + - name: Set up uv + uses: astral-sh/setup-uv@v5 + + - name: Set up Node/pnpm + uses: pnpm/action-setup@v4 + with: + run_install: false + + - name: Install Go quality tools + run: | + set -euo pipefail + go install golang.org/x/vuln/cmd/govulncheck@latest + go install honnef.co/go/tools/cmd/staticcheck@latest + curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b "$(go env GOPATH)/bin" + echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH" + + - name: Install Claude Code CLI + run: npm install -g @anthropic-ai/claude-code + + - name: Vendor the bump skill into the runner + run: | + set -euo pipefail + tmp="$(mktemp -d)" + git clone --depth 1 https://github.com/ericfitz/skills "$tmp/skills" + mkdir -p .claude/skills/bump + cp -R "$tmp/skills/deps/skills/bump/." .claude/skills/bump/ + test -f .claude/skills/bump/SKILL.md && echo "skill vendored" + + - name: Run headless bump (commit only, no push) + env: + CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + git config user.name "deps-bot" + git config user.email "deps-bot@users.noreply.github.com" + claude -p "/bump — run the dependency bump skill on the CURRENT checked-out branch only. Do NOT switch branches and do NOT ask me anything; you are running unattended in CI. Apply only safe patch/minor updates, run build/test/lint, bisect on failure, commit the safe subset, and print the manual-review plan. If there are no safe updates, make no commit and say so." \ + --dangerously-skip-permissions \ + --allowedTools "Bash,Read,Write,Edit,Glob,Grep" + + - name: Show result + run: | + git log --oneline -3 + git status -sb +``` + +- [ ] **Step 2: Statically validate the workflow** + +Run: `actionlint .github/workflows/deps-bump.yml` +Expected: no output (exit 0). Fix any reported syntax/type issues. + +- [ ] **Step 3: Commit the spike workflow** + +```bash +git add .github/workflows/deps-bump.yml +git commit -m "ci(deps): spike headless deps bump on one branch" +``` + +- [ ] **Step 4: Push the branch the workflow lives on and dispatch the run** + +> The workflow must exist on the branch you dispatch from. Push to a CI branch (e.g. `dev/1.4.0`) per the project's normal push process, then: + +```bash +gh workflow run deps-bump.yml --ref dev/1.4.0 -f branch=dev/1.4.0 +gh run watch "$(gh run list --workflow=deps-bump.yml --limit 1 --json databaseId --jq '.[0].databaseId')" +``` +Expected: the run completes green. In the "Run headless bump" log, the skill detects ecosystems, applies safe updates (or reports none), and build/test/lint pass. The "Show result" step shows either a new `chore(deps): ...` commit or a clean tree with a "no safe updates" message. + +- [ ] **Step 5: Record verified facts inline in the workflow** + +If `--dangerously-skip-permissions` was rejected or insufficient, switch to writing `.claude/settings.json` before the run: +```bash +mkdir -p .claude +printf '{\n "permissions": { "defaultMode": "bypassPermissions" }\n}\n' > .claude/settings.json +``` +Update the toolchain steps to match whatever the spike actually needed (e.g. if Node isn't a real ecosystem in tmi, drop the pnpm setup). Commit: +```bash +git add .github/workflows/deps-bump.yml .claude/settings.json +git commit -m "ci(deps): pin verified headless invocation for bump spike" +``` +Expected: a follow-up green dispatch confirming the pinned invocation. + +--- + +## Task 2: Open a PR with the bump result + +Extend the spike so a successful bump lands as a PR against the source branch (the agreed output model), and a no-op exits cleanly. + +**Files:** +- Modify: `.github/workflows/deps-bump.yml` (add working-branch + PR steps; raise `permissions`) + +**Interfaces:** +- Consumes: the verified `claude -p` step from Task 1. +- Produces: a PR per run when there are changes; the skill's plan in the PR body. + +- [ ] **Step 1: Raise workflow permissions** + +```yaml +permissions: + contents: write + pull-requests: write +``` + +- [ ] **Step 2: Create a working branch before the bump runs** + +Insert before the "Run headless bump" step: +```yaml + - name: Create working branch + id: wb + run: | + set -euo pipefail + WB="deps/auto-bump/${{ github.event.inputs.branch }}/${{ github.run_id }}" + git config user.name "deps-bot" + git config user.email "deps-bot@users.noreply.github.com" + git checkout -b "$WB" + echo "working_branch=$WB" >> "$GITHUB_OUTPUT" + echo "base_branch=${{ github.event.inputs.branch }}" >> "$GITHUB_OUTPUT" +``` + +- [ ] **Step 3: Capture the skill's plan output to a file** + +Change the bump step to tee Claude's stdout so the plan can go in the PR body: +```yaml + - name: Run headless bump (commit only) + env: + CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + claude -p "/bump — run the dependency bump skill on the CURRENT checked-out branch only. Do NOT switch branches and do NOT ask me anything; you are running unattended in CI. Apply only safe patch/minor updates, run build/test/lint, bisect on failure, commit the safe subset, and print the manual-review plan. If there are no safe updates, make no commit and say so." \ + --dangerously-skip-permissions \ + --allowedTools "Bash,Read,Write,Edit,Glob,Grep" | tee /tmp/bump-plan.txt +``` + +- [ ] **Step 4: Open a PR only if the bump produced commits** + +Replace the "Show result" step: +```yaml + - name: Open PR if there are changes + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + BASE="${{ steps.wb.outputs.base_branch }}" + WB="${{ steps.wb.outputs.working_branch }}" + if git diff --quiet "origin/$BASE".."$WB" 2>/dev/null && [ "$(git rev-list --count "origin/$BASE".."$WB")" -eq 0 ]; then + echo "No commits relative to $BASE — no safe updates. Skipping PR." + exit 0 + fi + git push origin "$WB" + { + echo "Automated dependency bump for \`$BASE\` (safe patch/minor only)." + echo + echo "Generated by the \`deps:bump\` skill running headless in CI." + echo + echo '
Manual-review plan from the skill' + echo + echo '```' + tail -c 60000 /tmp/bump-plan.txt + echo '```' + echo '
' + } > /tmp/pr-body.md + gh pr create --base "$BASE" --head "$WB" \ + --title "chore(deps): automated bump for $BASE" \ + --body-file /tmp/pr-body.md \ + --label dependencies +``` + +- [ ] **Step 5: Validate and dispatch** + +Run: `actionlint .github/workflows/deps-bump.yml` +Expected: exit 0. +Then commit, push, and dispatch as in Task 1 Step 4. Expected: a PR is opened against `dev/1.4.0` (if there are safe updates) with the plan in a collapsible section, OR the run logs "No commits … Skipping PR." + +- [ ] **Step 6: Commit** + +```bash +git add .github/workflows/deps-bump.yml +git commit -m "ci(deps): open a PR per run with the bump result" +``` + +--- + +## Task 3: Generalize the single-branch job (dispatch input) + +Make the job branch-agnostic so it can run against any single branch via `workflow_dispatch`, in preparation for the matrix. + +**Files:** +- Modify: `.github/workflows/deps-bump.yml` + +**Interfaces:** +- Consumes: the PR-producing job from Task 2. +- Produces: a job parameterized by a single `branch` value (later supplied by the matrix as `${{ matrix.branch }}`). + +- [ ] **Step 1: Replace hardcoded `github.event.inputs.branch` with a job-level env** + +Add at the top of the `bump` job so the same expression works for both dispatch and (later) matrix: +```yaml + bump: + runs-on: ubuntu-latest + env: + TARGET_BRANCH: ${{ matrix.branch || github.event.inputs.branch }} + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ env.TARGET_BRANCH }} + fetch-depth: 0 +``` +Replace every other `${{ github.event.inputs.branch }}` in the job with `${{ env.TARGET_BRANCH }}`. + +- [ ] **Step 2: Make the dispatch `branch` input free-form with a sane default** + +```yaml + workflow_dispatch: + inputs: + branch: + description: 'Branch to bump (a branch name, or "all")' + default: 'main' + type: string +``` + +- [ ] **Step 3: Validate, commit, dispatch against `main`** + +Run: `actionlint .github/workflows/deps-bump.yml` → exit 0. +```bash +git add .github/workflows/deps-bump.yml +git commit -m "ci(deps): parameterize bump job by target branch" +``` +Dispatch: `gh workflow run deps-bump.yml --ref dev/1.4.0 -f branch=main` and watch. +Expected: runs against `main`, opens a PR targeting `main` (or clean no-op). + +--- + +## Task 4: Branch discovery + matrix ("all branches independently") + +Add a `discover` job that emits the branch matrix, and drive `bump` from it. This delivers the "main / specific / all-independently, no cherry-pick or merge" requirement. + +**Files:** +- Modify: `.github/workflows/deps-bump.yml` + +**Interfaces:** +- Produces: `needs.discover.outputs.branches` — a JSON array of branch names. +- Consumes (in `bump`): `${{ fromJson(needs.discover.outputs.branches) }}` as `matrix.branch`. + +- [ ] **Step 1: Add the `discover` job** + +```yaml + discover: + runs-on: ubuntu-latest + outputs: + branches: ${{ steps.set.outputs.branches }} + steps: + - id: set + env: + GH_TOKEN: ${{ github.token }} + REPO: ${{ github.repository }} + EVENT: ${{ github.event_name }} + INPUT_BRANCH: ${{ github.event.inputs.branch }} + run: | + set -euo pipefail + all_branches() { + gh api "repos/$REPO/branches" --paginate --jq '.[].name' \ + | grep -E '^(main|dev/.*)$' || true + } + if [ "$EVENT" = "workflow_dispatch" ] && [ "$INPUT_BRANCH" != "all" ]; then + LIST="$INPUT_BRANCH" + else + LIST="$(all_branches)" + fi + BRANCHES="$(printf '%s\n' "$LIST" | grep -v '^$' | jq -R . | jq -cs .)" + echo "branches=$BRANCHES" >> "$GITHUB_OUTPUT" + echo "Discovered: $BRANCHES" +``` + +- [ ] **Step 2: Convert `bump` to a matrix job that needs `discover`** + +```yaml + bump: + needs: discover + if: ${{ needs.discover.outputs.branches != '[]' && needs.discover.outputs.branches != '' }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + branch: ${{ fromJson(needs.discover.outputs.branches) }} + env: + TARGET_BRANCH: ${{ matrix.branch }} +``` +(Remove the `matrix.branch || github.event.inputs.branch` fallback now that the matrix always supplies it.) + +- [ ] **Step 3: Add per-branch concurrency so re-runs don't collide** + +```yaml + concurrency: + group: deps-bump-${{ matrix.branch }} + cancel-in-progress: false +``` + +- [ ] **Step 4: Validate, commit, dispatch with `all`** + +Run: `actionlint .github/workflows/deps-bump.yml` → exit 0. +```bash +git add .github/workflows/deps-bump.yml +git commit -m "ci(deps): discover branches and fan out per-branch (matrix)" +``` +Dispatch: `gh workflow run deps-bump.yml --ref dev/1.4.0 -f branch=all` and watch. +Expected: `discover` lists `main` + `dev/*`; one independent `bump` job per branch; each opens its own PR (or no-ops). A failure on one branch does not abort the others (`fail-fast: false`). + +--- + +## Task 5: Scheduling + daily Dependabot-alert poll + +Add the weekly full run and the daily alert-gated run. + +**Files:** +- Modify: `.github/workflows/deps-bump.yml` + +**Interfaces:** +- Consumes: the `discover`/`bump` jobs from Task 4. +- Produces: scheduled triggers; `discover` returns `[]` on a daily poll with no open alerts (so `bump` is skipped by its `if`). + +- [ ] **Step 1: Add the schedule triggers** + +```yaml +on: + schedule: + - cron: '0 16 * * 1' # weekly full run, Mon 16:00 UTC (after Dependabot Monday) + - cron: '0 13 * * *' # daily Dependabot-alert poll + workflow_dispatch: + inputs: + branch: + description: 'Branch to bump (a branch name, or "all")' + default: 'main' + type: string +``` + +- [ ] **Step 2: Teach `discover` the three trigger modes** + +Replace the `discover` `run:` body: +```bash +set -euo pipefail +all_branches() { + gh api "repos/$REPO/branches" --paginate --jq '.[].name' \ + | grep -E '^(main|dev/.*)$' || true +} +open_alerts_count() { + gh api "repos/$REPO/dependabot/alerts?state=open&per_page=1" --jq 'length' 2>/dev/null || echo "ERR" +} + +if [ "$EVENT" = "workflow_dispatch" ] && [ "$INPUT_BRANCH" != "all" ]; then + LIST="$INPUT_BRANCH" +elif [ "$EVENT" = "schedule" ] && [ "$SCHED" = "0 13 * * *" ]; then + CNT="$(open_alerts_count)" + if [ "$CNT" = "ERR" ]; then + echo "::error::Could not read Dependabot alerts (token scope?). See Task 5 Step 3." + LIST="" + elif [ "$CNT" -gt 0 ] 2>/dev/null; then + echo "Open Dependabot alerts present ($CNT) — running full set." + LIST="$(all_branches)" + else + echo "No open Dependabot alerts — nothing to do." + LIST="" + fi +else + # weekly schedule, or dispatch with branch=all + LIST="$(all_branches)" +fi +BRANCHES="$(printf '%s\n' "$LIST" | grep -v '^$' | jq -R . | jq -cs .)" +echo "branches=$BRANCHES" >> "$GITHUB_OUTPUT" +echo "Discovered: $BRANCHES" +``` +Add `SCHED: ${{ github.event.schedule }}` to the step's `env:`. + +- [ ] **Step 3: Verify the Dependabot-alerts token, add PAT only if needed** + +Test whether `github.token` can read alerts: +```bash +gh workflow run deps-bump.yml --ref dev/1.4.0 # fires as dispatch=all; does NOT exercise the poll path +``` +To exercise the poll path directly, temporarily add `workflow_dispatch` input `force_poll` and branch on it, OR test the API locally: +```bash +GH_TOKEN=$(gh auth token) gh api "repos/ericfitz/tmi/dependabot/alerts?state=open&per_page=1" --jq 'length' +``` +Expected: a number. If the in-workflow `github.token` call logs the `::error::` (403/empty), create a fine-grained PAT with **Dependabot alerts: read**, add it as secret `DEPS_ALERTS_TOKEN`, and set in the `discover` step env: +```yaml + GH_TOKEN: ${{ secrets.DEPS_ALERTS_TOKEN || github.token }} +``` + +- [ ] **Step 4: Validate and commit** + +Run: `actionlint .github/workflows/deps-bump.yml` → exit 0. +```bash +git add .github/workflows/deps-bump.yml +git commit -m "ci(deps): add weekly schedule + daily alert-gated poll" +``` +Expected: manual `branch=all` dispatch still works; the daily/weekly crons are registered (visible under the repo Actions tab → the workflow → "This workflow has scheduled events"). + +--- + +## Task 6: Hardening, docs, and finalize + +**Files:** +- Modify: `.github/workflows/deps-bump.yml` +- Create: wiki page note (GitHub Wiki, per tmi docs policy — not `docs/`) + +**Interfaces:** none new. + +- [ ] **Step 1: Make a bump-step failure surface clearly without aborting siblings** + +Confirm `strategy.fail-fast: false` is set (Task 4). Add a guard so a Claude failure still reports: +```yaml + - name: Run headless bump (commit only) + id: bump + continue-on-error: false + env: + CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + if [ -z "${CLAUDE_CODE_OAUTH_TOKEN:-}" ]; then + echo "::error::CLAUDE_CODE_OAUTH_TOKEN is empty (secret missing on this ref)."; exit 1 + fi + claude -p "...prompt as in Task 2..." \ + --dangerously-skip-permissions \ + --allowedTools "Bash,Read,Write,Edit,Glob,Grep" | tee /tmp/bump-plan.txt +``` + +- [ ] **Step 2: Add a top-level guard against scheduled runs on forks** + +```yaml +jobs: + discover: + if: ${{ github.repository == 'ericfitz/tmi' }} + runs-on: ubuntu-latest +``` + +- [ ] **Step 3: Document the workflow in the GitHub Wiki** + +Add a wiki page (e.g. "CI: Automated Dependency Bumps") covering: triggers, the `branch`/`all` dispatch input, where PRs land, the `CLAUDE_CODE_OAUTH_TOKEN`/`DEPS_ALERTS_TOKEN` secrets, and how to disable (comment out the `schedule:` block). Do **not** add this under `docs/` (tmi policy: wiki only; `docs/superpowers/` is for specs/plans only). + +- [ ] **Step 4: Final static validation + commit** + +Run: `actionlint .github/workflows/deps-bump.yml` → exit 0. +```bash +git add .github/workflows/deps-bump.yml +git commit -m "ci(deps): harden deps-bump workflow (fork guard, token check)" +``` + +- [ ] **Step 5: Full acceptance dispatch** + +```bash +gh workflow run deps-bump.yml --ref dev/1.4.0 -f branch=all +gh run watch "$(gh run list --workflow=deps-bump.yml --limit 1 --json databaseId --jq '.[0].databaseId')" +``` +Expected (success criteria from the spec): +- `discover` emits `main` + `dev/*`. +- One independent PR per branch with safe updates (or a clean no-op log). +- Existing PR checks (`security-deps-gate.yml`, `security.yml`, `codeql.yml`) run on each PR. + +--- + +## Self-Review + +**Spec coverage:** +- "main / specific / all-independently, no cherry-pick/merge" → Tasks 3–4 (matrix, per-branch jobs, no cross-branch ops). ✓ +- "schedule on GitHub" → Task 5 weekly cron. ✓ +- "triggered by Dependabot alerts" → Task 5 daily poll (documented approximation; spec Risk 3). ✓ +- PR-per-branch output → Task 2. ✓ +- OAuth auth → Tasks 0/1. ✓ +- Skill-in-CI risk → Task 1 spike (vendor at runtime). ✓ +- Alert-token risk → Task 5 Step 3. ✓ +- Toolchain for build/test/lint → Task 1 setup steps. ✓ + +**Placeholder scan:** Prompts, YAML, and commands are concrete. The two explicitly-flagged unknowns (permission flag; alerts token) have verification steps and named fallbacks rather than silent TBDs. ✓ + +**Type consistency:** `discover.outputs.branches` (JSON array) is produced in Tasks 4–5 and consumed via `fromJson(...)` as `matrix.branch`; `TARGET_BRANCH` env is introduced in Task 3 and used consistently thereafter; `steps.wb.outputs.{working_branch,base_branch}` defined and used in Task 2. ✓ diff --git a/docs/superpowers/specs/2026-06-18-deps-bump-automation-design.md b/docs/superpowers/specs/2026-06-18-deps-bump-automation-design.md new file mode 100644 index 00000000..785e9bfa --- /dev/null +++ b/docs/superpowers/specs/2026-06-18-deps-bump-automation-design.md @@ -0,0 +1,126 @@ +# Design: Automate the `deps:bump` skill on GitHub Actions + +**Date:** 2026-06-18 +**Repo (first target):** `ericfitz/tmi` (pattern generalizes to `tmi-ux` later) +**Status:** Awaiting user review → implementation plan + +## Goal + +Run the existing Claude Code `deps:bump` skill automatically, so dependency +updates land as reviewable PRs without a human kicking off each run. + +### Requirements (from the user) + +1. Run against **main (default), a specific branch, or all branches + independently** — with **no cherry-pick and no merge** between branches. + Each branch's bump operates only on that branch. +2. Run **automatically on a schedule** on GitHub, **and** be **triggered by + Dependabot alerts** (approximated — see Constraints). + +### Decisions (locked with the user) + +| Topic | Decision | +|-------|----------| +| Output model | **Open a PR per branch** (commit to a working branch, PR targets the source branch). | +| "All branches" scope | **`main` + every `dev/*` release branch** (discovered dynamically). | +| Claude auth | **`CLAUDE_CODE_OAUTH_TOKEN`** (user's subscription via `claude setup-token`); no API billing. | +| Schedule | **Weekly full run** (Mon, after Dependabot's Monday run) + **daily alert poll**. | +| Alert trigger | **Daily poll** that runs the bump only when open Dependabot alerts exist. | + +## What `deps:bump` does (and doesn't) + +The skill (`efitz-skills` marketplace, `deps/skills/bump/SKILL.md`): +- Auto-detects ecosystems (Go / Python / Node). +- Loads exclusions from `## Bump Exclusions` in `CLAUDE.md`, `.bump-config.json`, + and `// pinned:` / `# pinned:` comments. +- Applies **safe patch/minor** updates only (majors → manual-review plan). +- Runs build + test + lint; **bisects** to isolate a bad package on failure. +- **Commits** the safe subset and prints a prioritized manual-review plan. + +It **does not push** and **does not open PRs** — automation supplies that step. +Its Phase 1 is **interactive** (asks about switching branches) — the CI prompt +pins it to the current branch and forbids prompting. + +tmi validation commands (per tmi `CLAUDE.md`): `make build-server`, +`make test-unit` (fast, **no external deps** — CI-friendly), `make lint` +(`uv run scripts/lint.py` + golangci-lint + staticcheck checks). + +## Architecture + +Single workflow: **`.github/workflows/deps-bump.yml`**. + +### Triggers (`on:`) +- `schedule: '0 16 * * 1'` — weekly full run (Mon 16:00 UTC, after the Monday + Dependabot run). +- `schedule: '0 13 * * *'` — daily alert poll. +- `workflow_dispatch` — inputs: `branch` (specific name or `all`), + `ecosystem` (optional: go/node/python). + +Branch logic distinguishes the run type via `github.event.schedule` / +`github.event_name`. + +### Job 1 — `discover` (matrix builder) +Outputs a JSON array of target branches: +- Weekly / dispatch `all` → `main` + `dev/*` (via `gh api repos/$REPO/branches`). +- Dispatch specific branch → just that branch. +- Daily poll → query open Dependabot alerts; map to affected branches; if none, + output `[]` so the bump job is skipped. + +### Job 2 — `bump` (matrix over `discover` output) +`strategy.fail-fast: false`; `concurrency` keyed per branch (no colliding runs). +Per branch: +1. `actions/checkout` the target branch (full history). +2. Set up toolchain: Go, uv, Node/pnpm; install `govulncheck`, + `golangci-lint`, `staticcheck`, `gh`. +3. Make the `deps:bump` skill available to a headless Claude Code run + (see Risk 1 for the mechanism). +4. `git checkout -b deps/auto-bump//`. +5. Run Claude headless with a pinned prompt: *"Run the deps bump skill on the + current branch. Never switch branches, never prompt. Commit the safe + updates and output the manual-review plan."* +6. If a commit was produced (working branch differs from base): push the + working branch and `gh pr create --base ` with the skill's + manual-review **plan in the PR body**. If no changes: skip. + +Existing PR checks (`security-deps-gate.yml`, `security.yml`, `codeql.yml`) +run on the PR before merge — this is the review gate. + +### Auth & permissions +- Repo secret `CLAUDE_CODE_OAUTH_TOKEN` (from `claude setup-token`). +- Workflow `permissions: { contents: write, pull-requests: write }`. +- Dependabot-alert read may require a fine-grained **PAT** secret rather than + the default `GITHUB_TOKEN` (see Risk 2). + +## Constraints & Risks (resolve in implementation plan) + +1. **Skill-in-CI (main risk).** The skill ships in the `efitz-skills` + marketplace plugin; it must be available to a headless runner. Candidate + approaches: (a) install the marketplace/plugin in CI, or (b) vendor the + `bump` SKILL.md into the runner workspace and point Claude at it. Pick and + validate one in the plan. +2. **Dependabot alerts API auth.** Reading `repos/$REPO/dependabot/alerts` may + need a token with Dependabot-alerts: read, which the default `GITHUB_TOKEN` + may not grant. Plan: confirm; add a fine-grained PAT secret only if needed. +3. **`dependabot_alert` is not a native Actions trigger** (webhook event only; + open feature request). Hence the daily poll. Acknowledged tradeoff: + up to ~24h latency from alert to bump run. +4. **PRs opened with `GITHUB_TOKEN` don't trigger downstream workflows** by + default. Since merging is manual and the security gates run on `push`/PR via + their own triggers, confirm the gates fire; if not, use a PAT for `pr create`. +5. **CI cost/usage.** Headless Claude runs consume subscription usage; weekly + + daily-when-alerts keeps volume modest. `fail-fast: false` isolates per-branch + failures. + +## Out of scope (this iteration) +- Auto-merging PRs. +- Major-version upgrades (skill defers these to the manual plan by design). +- tmi-ux and other repos (replicate the pattern after tmi is proven). +- Cross-branch propagation (explicitly excluded: no cherry-pick / merge). + +## Success criteria +- Manual `workflow_dispatch` on a chosen branch opens a PR (or cleanly reports + "no safe updates") with passing toolchain setup. +- Weekly schedule fans out across `main` + `dev/*`, one independent PR per + branch that has safe updates. +- Daily poll is a no-op when there are no open alerts and opens PRs when there + are. From 229894c380bf53efadb6c99c0ff22fbab8915a75 Mon Sep 17 00:00:00 2001 From: Eric Fitzgerald Date: Thu, 18 Jun 2026 22:22:53 +0700 Subject: [PATCH 3/7] ci(deps): spike headless deps bump (push-triggered iteration) Minimal single-job workflow: checkout, set up Go/uv/pnpm + Go quality tools, install Claude Code CLI, vendor the bump skill from ericfitz/skills, run it headless to commit safe updates. Triggered on push to this feature branch for iteration; no push-back yet (contents: read). PR output + dispatch/schedule come in later tasks. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01Q6YFSYL252h71BH5ZL8QqT --- .github/workflows/deps-bump.yml | 82 +++++++++++++++++++ .../plans/2026-06-18-deps-bump-automation.md | 12 ++- 2 files changed, 87 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/deps-bump.yml diff --git a/.github/workflows/deps-bump.yml b/.github/workflows/deps-bump.yml new file mode 100644 index 00000000..f67939b1 --- /dev/null +++ b/.github/workflows/deps-bump.yml @@ -0,0 +1,82 @@ +name: Dependency Bump (Claude) + +on: + # Iteration trigger only. workflow_dispatch & schedule fire only from the + # default branch, so while developing on this feature branch we trigger on + # pushes to it. This block is replaced by workflow_dispatch + schedule + # (Task 5) before the workflow is merged to main. + push: + branches: [feature/deps-bump-automation] + +permissions: + contents: read # spike only reads + commits locally in the runner; no push yet + +jobs: + bump: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: go.mod + + - name: Set up uv + uses: astral-sh/setup-uv@v5 + + - name: Set up pnpm + uses: pnpm/action-setup@v4 + with: + version: 9 # pnpm-lock.yaml is lockfileVersion 9.0; package.json has no packageManager field + run_install: false + + - name: Install Go quality tools + run: | + set -euo pipefail + go install golang.org/x/vuln/cmd/govulncheck@latest + go install honnef.co/go/tools/cmd/staticcheck@latest + # golangci-lint via goinstall, pinned to the version tmi's security.yml uses + go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.12.2 + echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH" + + - name: Install Claude Code CLI + run: npm install -g @anthropic-ai/claude-code + + - name: Vendor the bump skill into the runner + env: + # Pin ericfitz/skills to a reviewed commit so the high-privilege agent + # run below cannot execute an unreviewed change to the skill. Bump this + # SHA deliberately when adopting skill updates. + SKILL_SHA: "804ee39f7d594e896f6dab8501b59cb92556b8df" + run: | + set -euo pipefail + tmp="$(mktemp -d)" + git clone --filter=blob:none https://github.com/ericfitz/skills "$tmp/skills" + git -C "$tmp/skills" checkout -q "$SKILL_SHA" + mkdir -p .claude/skills/bump + cp -R "$tmp/skills/deps/skills/bump/." .claude/skills/bump/ + test -f .claude/skills/bump/SKILL.md && echo "skill vendored @ $SKILL_SHA" + + - name: Run headless bump (commit only, no push) + env: + CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + if [ -z "${CLAUDE_CODE_OAUTH_TOKEN:-}" ]; then + echo "::error::CLAUDE_CODE_OAUTH_TOKEN is empty (secret missing on this ref)." + exit 1 + fi + git config user.name "deps-bot" + git config user.email "deps-bot@users.noreply.github.com" + claude -p "/bump — run the dependency bump skill on the CURRENT checked-out branch only. Do NOT switch branches and do NOT ask me anything; you are running unattended in CI. Apply only safe patch/minor updates, run build/test/lint, bisect on failure, commit the safe subset, and print the manual-review plan. If there are no safe updates, make no commit and say so." \ + --dangerously-skip-permissions \ + --allowedTools "Bash,Read,Write,Edit,Glob,Grep" + + - name: Show result + run: | + git log --oneline -3 + git status -sb diff --git a/docs/superpowers/plans/2026-06-18-deps-bump-automation.md b/docs/superpowers/plans/2026-06-18-deps-bump-automation.md index 5610b784..9f05e35e 100644 --- a/docs/superpowers/plans/2026-06-18-deps-bump-automation.md +++ b/docs/superpowers/plans/2026-06-18-deps-bump-automation.md @@ -82,12 +82,11 @@ Prove the headless skill run works end-to-end on `dev/1.4.0` with a minimal, man name: Dependency Bump (Claude) on: - workflow_dispatch: - inputs: - branch: - description: 'Branch to bump' - default: 'dev/1.4.0' - type: string + # Iteration trigger: workflow_dispatch & schedule only fire from the default + # branch, so during development we trigger on pushes to the feature branch. + # Replaced by workflow_dispatch + schedule in Task 5 before merging to main. + push: + branches: [feature/deps-bump-automation] permissions: contents: read # spike only reads + commits locally; no push yet @@ -98,7 +97,6 @@ jobs: steps: - uses: actions/checkout@v4 with: - ref: ${{ github.event.inputs.branch }} fetch-depth: 0 - name: Set up Go From 8df74f29efa4472e17783f60be15fce6ade5f1b4 Mon Sep 17 00:00:00 2001 From: Eric Fitzgerald Date: Thu, 18 Jun 2026 22:58:14 +0700 Subject: [PATCH 4/7] ci(deps): open a PR per run (working branch + scoped agent) Evolve the spike into the PR output model: - checkout with persist-credentials:false so the --dangerously-skip-permissions agent cannot push or perform GitHub writes - agent step runs without GH_TOKEN and under a strict prompt: commit only, no push / session-completion / issue-filing / security-review / PR - a separate step pushes the working branch via the job token and opens a PR targeting the source branch, with the skill's plan in the body Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01Q6YFSYL252h71BH5ZL8QqT --- .github/workflows/deps-bump.yml | 64 +++++++++++++++++++++++++++------ 1 file changed, 54 insertions(+), 10 deletions(-) diff --git a/.github/workflows/deps-bump.yml b/.github/workflows/deps-bump.yml index f67939b1..906a49bb 100644 --- a/.github/workflows/deps-bump.yml +++ b/.github/workflows/deps-bump.yml @@ -9,7 +9,8 @@ on: branches: [feature/deps-bump-automation] permissions: - contents: read # spike only reads + commits locally in the runner; no push yet + contents: write # push the working branch + pull-requests: write # open the PR jobs: bump: @@ -18,6 +19,10 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 + # The headless agent runs with --dangerously-skip-permissions; do NOT + # leave a push credential in its reach. The "Open PR" step authenticates + # explicitly with the job token instead. + persist-credentials: false - name: Set up Go uses: actions/setup-go@v5 @@ -60,23 +65,62 @@ jobs: cp -R "$tmp/skills/deps/skills/bump/." .claude/skills/bump/ test -f .claude/skills/bump/SKILL.md && echo "skill vendored @ $SKILL_SHA" - - name: Run headless bump (commit only, no push) + - name: Create working branch + id: wb + env: + BASE: ${{ github.ref_name }} + run: | + set -euo pipefail + WB="deps/auto-bump/${BASE}/${{ github.run_id }}" + git config user.name "deps-bot" + git config user.email "deps-bot@users.noreply.github.com" + git checkout -b "$WB" + { + echo "base=$BASE" + echo "wb=$WB" + } >> "$GITHUB_OUTPUT" + + - name: Run headless bump (commit only — no push, no PR, no session-completion) env: CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} - GH_TOKEN: ${{ github.token }} + # Intentionally NO GH_TOKEN: the agent must not perform GitHub writes. run: | set -euo pipefail if [ -z "${CLAUDE_CODE_OAUTH_TOKEN:-}" ]; then echo "::error::CLAUDE_CODE_OAUTH_TOKEN is empty (secret missing on this ref)." exit 1 fi - git config user.name "deps-bot" - git config user.email "deps-bot@users.noreply.github.com" - claude -p "/bump — run the dependency bump skill on the CURRENT checked-out branch only. Do NOT switch branches and do NOT ask me anything; you are running unattended in CI. Apply only safe patch/minor updates, run build/test/lint, bisect on failure, commit the safe subset, and print the manual-review plan. If there are no safe updates, make no commit and say so." \ + claude -p "Run the dependency bump skill (/bump) on the CURRENT checked-out git branch ONLY. You are running UNATTENDED in CI under a STRICT scope. DO: apply only safe patch/minor dependency updates; run the project build/test/lint; bisect out any update that breaks them; make exactly ONE local commit with the safe updates; then STOP. DO NOT: push; run any session-completion / 'landing the plane' / end-of-session workflow; file or close GitHub issues; run a security review; open a pull request; switch branches; ask questions. A separate CI step handles pushing and the PR. If there are no safe updates, make NO commit and say so. Finally, print the manual-review plan for anything not auto-applied." \ --dangerously-skip-permissions \ - --allowedTools "Bash,Read,Write,Edit,Glob,Grep" + --allowedTools "Bash,Read,Write,Edit,Glob,Grep" | tee /tmp/bump-plan.txt - - name: Show result + - name: Open PR if there are changes + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BASE: ${{ steps.wb.outputs.base }} + WB: ${{ steps.wb.outputs.wb }} + REPO: ${{ github.repository }} run: | - git log --oneline -3 - git status -sb + set -euo pipefail + if [ "$(git rev-list --count "origin/${BASE}..HEAD")" -eq 0 ]; then + echo "No commits relative to ${BASE} — no safe updates. Skipping PR." + exit 0 + fi + # persist-credentials was false, so push with the job token explicitly. + git push "https://x-access-token:${GH_TOKEN}@github.com/${REPO}.git" "HEAD:refs/heads/${WB}" + { + echo "Automated dependency bump for \`${BASE}\` (safe patch/minor only)." + echo + echo "Generated by the \`deps:bump\` skill running headless in CI. Review before merging." + echo + echo '
Skill output / manual-review plan' + echo + echo '```' + tail -c 60000 /tmp/bump-plan.txt + echo '```' + echo '
' + } > /tmp/pr-body.md + gh pr create --repo "${REPO}" --base "${BASE}" --head "${WB}" \ + --title "chore(deps): automated bump for ${BASE}" \ + --body-file /tmp/pr-body.md \ + --label dependencies From b907bead83890a044712dc31261d593477dee825 Mon Sep 17 00:00:00 2001 From: Eric Fitzgerald Date: Thu, 18 Jun 2026 23:16:30 +0700 Subject: [PATCH 5/7] ci(deps): mint ephemeral GitHub App token for push + PR (job token read-only) Replace GITHUB_TOKEN writes with an ephemeral GitHub App installation token minted AFTER the agent step, so no write credential exists while the --dangerously-skip-permissions agent runs. Job permissions reduced to contents:read. App-authored PRs also trigger the repo's security gates. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01Q6YFSYL252h71BH5ZL8QqT --- .github/workflows/deps-bump.yml | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/.github/workflows/deps-bump.yml b/.github/workflows/deps-bump.yml index 906a49bb..11e18453 100644 --- a/.github/workflows/deps-bump.yml +++ b/.github/workflows/deps-bump.yml @@ -9,8 +9,8 @@ on: branches: [feature/deps-bump-automation] permissions: - contents: write # push the working branch - pull-requests: write # open the PR + contents: read # the job's own GITHUB_TOKEN only reads (checkout). All writes + # go through the ephemeral GitHub App token minted after the agent step. jobs: bump: @@ -94,9 +94,19 @@ jobs: --dangerously-skip-permissions \ --allowedTools "Bash,Read,Write,Edit,Glob,Grep" | tee /tmp/bump-plan.txt + # Minted AFTER the agent step so the write-capable token never exists while + # the --dangerously-skip-permissions agent is running. Ephemeral (~1h), + # scoped to this App's Contents + Pull-requests permissions on this repo. + - name: Mint GitHub App token + id: app-token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ secrets.DEPS_BOT_APP_ID }} + private-key: ${{ secrets.DEPS_BOT_APP_PRIVATE_KEY }} + - name: Open PR if there are changes env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ steps.app-token.outputs.token }} BASE: ${{ steps.wb.outputs.base }} WB: ${{ steps.wb.outputs.wb }} REPO: ${{ github.repository }} From 1463d652168df317182e2935ad13dcc621d315cc Mon Sep 17 00:00:00 2001 From: Eric Fitzgerald Date: Fri, 19 Jun 2026 02:22:58 +0700 Subject: [PATCH 6/7] =?UTF-8?q?ci(deps):=20production=20triggers=20?= =?UTF-8?q?=E2=80=94=20discover/matrix,=20weekly=20schedule=20+=20daily=20?= =?UTF-8?q?alert=20poll,=20dispatch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Tasks 3-5: add a discover job that emits the target-branch matrix (main + dev/*), fan out bump as a fail-fast:false matrix (one independent PR per branch, no cross-branch ops), and add workflow_dispatch (branch input) + weekly schedule + daily Dependabot-alert poll. Fork-guarded. The push trigger is a temporary dev self-test (discover emits just this branch) and is removed before merge to main. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01Q6YFSYL252h71BH5ZL8QqT --- .github/workflows/deps-bump.yml | 83 +++++++++++++++++++++++++++++---- 1 file changed, 74 insertions(+), 9 deletions(-) diff --git a/.github/workflows/deps-bump.yml b/.github/workflows/deps-bump.yml index 11e18453..65b290fd 100644 --- a/.github/workflows/deps-bump.yml +++ b/.github/workflows/deps-bump.yml @@ -1,27 +1,92 @@ name: Dependency Bump (Claude) on: - # Iteration trigger only. workflow_dispatch & schedule fire only from the - # default branch, so while developing on this feature branch we trigger on - # pushes to it. This block is replaced by workflow_dispatch + schedule - # (Task 5) before the workflow is merged to main. + schedule: + - cron: '0 16 * * 1' # weekly full run, Mon 16:00 UTC (after the Monday Dependabot run) + - cron: '0 13 * * *' # daily Dependabot-alert poll + workflow_dispatch: + inputs: + branch: + description: 'Branch to bump (a branch name, or "all")' + default: 'main' + type: string push: + # TEMPORARY dev self-test trigger. Removed before merge to main — schedule and + # workflow_dispatch only fire from the default branch. branches: [feature/deps-bump-automation] permissions: - contents: read # the job's own GITHUB_TOKEN only reads (checkout). All writes - # go through the ephemeral GitHub App token minted after the agent step. + contents: read + +concurrency: + group: deps-bump-${{ github.workflow }}-${{ github.event_name }} + cancel-in-progress: false jobs: + discover: + if: ${{ github.repository == 'ericfitz/tmi' }} + runs-on: ubuntu-latest + outputs: + branches: ${{ steps.set.outputs.branches }} + steps: + - id: set + env: + GH_TOKEN: ${{ secrets.DEPS_ALERTS_TOKEN || github.token }} + REPO: ${{ github.repository }} + EVENT: ${{ github.event_name }} + SCHED: ${{ github.event.schedule }} + INPUT_BRANCH: ${{ github.event.inputs.branch }} + REF_NAME: ${{ github.ref_name }} + run: | + set -euo pipefail + all_branches() { + gh api "repos/$REPO/branches" --paginate --jq '.[].name' | grep -E '^(main|dev/.*)$' || true + } + open_alerts_count() { + gh api "repos/$REPO/dependabot/alerts?state=open&per_page=1" --jq 'length' 2>/dev/null || echo "ERR" + } + if [ "$EVENT" = "push" ]; then + LIST="$REF_NAME" # dev self-test: just this branch + elif [ "$EVENT" = "workflow_dispatch" ] && [ "$INPUT_BRANCH" != "all" ]; then + LIST="$INPUT_BRANCH" + elif [ "$EVENT" = "schedule" ] && [ "$SCHED" = "0 13 * * *" ]; then + CNT="$(open_alerts_count)" + if [ "$CNT" = "ERR" ]; then + echo "::warning::Could not read Dependabot alerts (token scope). Daily poll is a no-op until DEPS_ALERTS_TOKEN is set." + LIST="" + elif [ "$CNT" -gt 0 ] 2>/dev/null; then + echo "Open Dependabot alerts present — running the full branch set." + LIST="$(all_branches)" + else + echo "No open Dependabot alerts — nothing to do." + LIST="" + fi + else + LIST="$(all_branches)" # weekly schedule, or dispatch with branch=all + fi + BRANCHES="$(printf '%s\n' "$LIST" | grep -v '^$' | jq -R . | jq -cs .)" + echo "branches=$BRANCHES" >> "$GITHUB_OUTPUT" + echo "Discovered branches: $BRANCHES" + bump: + needs: discover + if: ${{ needs.discover.outputs.branches != '[]' && needs.discover.outputs.branches != '' }} runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + branch: ${{ fromJson(needs.discover.outputs.branches) }} + concurrency: + group: deps-bump-run-${{ matrix.branch }} + cancel-in-progress: false steps: - uses: actions/checkout@v4 with: + ref: ${{ matrix.branch }} fetch-depth: 0 # The headless agent runs with --dangerously-skip-permissions; do NOT # leave a push credential in its reach. The "Open PR" step authenticates - # explicitly with the job token instead. + # explicitly with the App token instead. persist-credentials: false - name: Set up Go @@ -68,7 +133,7 @@ jobs: - name: Create working branch id: wb env: - BASE: ${{ github.ref_name }} + BASE: ${{ matrix.branch }} run: | set -euo pipefail WB="deps/auto-bump/${BASE}/${{ github.run_id }}" @@ -116,7 +181,7 @@ jobs: echo "No commits relative to ${BASE} — no safe updates. Skipping PR." exit 0 fi - # persist-credentials was false, so push with the job token explicitly. + # persist-credentials was false, so push with the App token explicitly. git push "https://x-access-token:${GH_TOKEN}@github.com/${REPO}.git" "HEAD:refs/heads/${WB}" { echo "Automated dependency bump for \`${BASE}\` (safe patch/minor only)." From 6a312ba8cfc50df58885191c086e3f30854eca90 Mon Sep 17 00:00:00 2001 From: Eric Fitzgerald Date: Fri, 19 Jun 2026 11:56:09 +0700 Subject: [PATCH 7/7] ci(deps): remove temporary push self-test trigger (schedule + dispatch only) Drop the dev-only push trigger and its discover branch now that the discover/matrix path is validated. Production triggers: weekly schedule, daily alert poll, and manual workflow_dispatch (active once merged to main). Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01Q6YFSYL252h71BH5ZL8QqT --- .github/workflows/deps-bump.yml | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/.github/workflows/deps-bump.yml b/.github/workflows/deps-bump.yml index 65b290fd..453dace1 100644 --- a/.github/workflows/deps-bump.yml +++ b/.github/workflows/deps-bump.yml @@ -10,10 +10,6 @@ on: description: 'Branch to bump (a branch name, or "all")' default: 'main' type: string - push: - # TEMPORARY dev self-test trigger. Removed before merge to main — schedule and - # workflow_dispatch only fire from the default branch. - branches: [feature/deps-bump-automation] permissions: contents: read @@ -36,7 +32,6 @@ jobs: EVENT: ${{ github.event_name }} SCHED: ${{ github.event.schedule }} INPUT_BRANCH: ${{ github.event.inputs.branch }} - REF_NAME: ${{ github.ref_name }} run: | set -euo pipefail all_branches() { @@ -45,9 +40,7 @@ jobs: open_alerts_count() { gh api "repos/$REPO/dependabot/alerts?state=open&per_page=1" --jq 'length' 2>/dev/null || echo "ERR" } - if [ "$EVENT" = "push" ]; then - LIST="$REF_NAME" # dev self-test: just this branch - elif [ "$EVENT" = "workflow_dispatch" ] && [ "$INPUT_BRANCH" != "all" ]; then + if [ "$EVENT" = "workflow_dispatch" ] && [ "$INPUT_BRANCH" != "all" ]; then LIST="$INPUT_BRANCH" elif [ "$EVENT" = "schedule" ] && [ "$SCHED" = "0 13 * * *" ]; then CNT="$(open_alerts_count)"