Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 0 additions & 154 deletions .github/workflows/ambient-version-agent.yml

This file was deleted.

41 changes: 28 additions & 13 deletions .github/workflows/nextjs-version-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
# Use the release PAT so the branch push + PR are authored by a real
# identity. A PR opened by GITHUB_TOKEN does NOT trigger CI (ci.yml's
# required checks never run) and a bot-merge does NOT fire the post-merge
# tag/publish chain — both are GitHub's anti-recursion rule. Falls back to
# GITHUB_TOKEN when RELEASE_PAT is unset (PR opens, but you merge by hand).
token: ${{ secrets.RELEASE_PAT || github.token }}

- name: Setup pnpm
uses: pnpm/action-setup@v4
Expand Down Expand Up @@ -201,7 +208,8 @@ jobs:
if: steps.check_version.outputs.needs_update == 'true' && steps.branch_check.outputs.branch_exists != 'true'
id: create_pr
env:
GH_TOKEN: ${{ github.token }}
# PAT so the PR triggers ci.yml (the required checks that gate the merge).
GH_TOKEN: ${{ secrets.RELEASE_PAT || github.token }}
run: |
MEMORY_STATUS="${{ steps.test_memory.outcome }}"
REDIS_STATUS="${{ steps.test_redis.outcome }}"
Expand Down Expand Up @@ -240,19 +248,26 @@ jobs:
echo "pr_url=$PR_URL" >> $GITHUB_OUTPUT
echo "all_tests_passed=$ALL_TESTS_PASSED" >> $GITHUB_OUTPUT

- name: Hand off to the ambient agent
- name: Enable auto-merge
if: steps.check_version.outputs.needs_update == 'true' && steps.branch_check.outputs.branch_exists != 'true'
env:
GH_TOKEN: ${{ github.token }}
GH_TOKEN: ${{ secrets.RELEASE_PAT }}
run: |
# The ambient agent (Claude Code, ambient-version-agent.yml) reviews this PR:
# runs the full suite, writes tests for any new cache behavior, then approves +
# merges if clean or flags it for a human. We dispatch it explicitly because a
# PR opened by GITHUB_TOKEN does not trigger pull_request workflows, and
# workflow_dispatch is exempt from that anti-recursion rule.
PR_NUMBER=$(gh pr view nextjs-${{ steps.check_version.outputs.latest_version }} --json number --jq .number)
if gh workflow run ambient-version-agent.yml -f pr_number="$PR_NUMBER"; then
echo "🤖 Dispatched ambient agent for PR #$PR_NUMBER"
else
echo "⚠️ Could not dispatch the agent; its scheduled run will pick this PR up."
BRANCH="nextjs-${{ steps.check_version.outputs.latest_version }}"

# No agent, no human: the PR ships itself once the required checks pass
# (lint-and-typecheck, unit-tests, and test-summary — which is green only if
# the full e2e matrix passes: memory, redis, valkey, elasticache). The e2e
# suite IS the reviewer.
if [ -z "$GH_TOKEN" ]; then
echo "::warning::RELEASE_PAT not set — PR opened, but auto-merge is off."
echo "Add a RELEASE_PAT secret (Contents + Pull requests: write) for"
echo "hands-off releases, or merge $BRANCH yourself once CI is green."
echo "See docs/auto-release.md."
exit 0
fi

# Auto-merge must be enabled by the PAT (a real identity): a merge performed
# via GITHUB_TOKEN would not fire tag-on-version-merge.yml.
gh pr merge "$BRANCH" --squash --auto --delete-branch
echo "🚀 Auto-merge enabled for $BRANCH — it ships the moment CI is green."
67 changes: 0 additions & 67 deletions docs/ambient-agent.md

This file was deleted.

71 changes: 71 additions & 0 deletions docs/auto-release.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Autonomous releases

This package keeps itself current with Next.js with **no human and no paid
services**. The end-to-end loop:

```
nextjs-version-check.yml (daily cron)
detects a new Next.js → branch nextjs-<v> → bump next + package version
→ run e2e (memory + redis) → open a PR (as the RELEASE_PAT identity)
└─ ci.yml runs the required checks on the PR:
lint-and-typecheck · unit-tests · test-summary
(test-summary is green only if the full e2e matrix passes:
memory, redis, valkey, elasticache)
└─ auto-merge is enabled on the PR
checks green → it squash-merges itself → branch deleted
└─ tag-on-version-merge.yml → tag v<v> → dispatches publish.yml
└─ npm publish (OIDC, tokenless) + GitHub release
```

**The e2e suite is the reviewer.** If the matrix is green the bump ships; if it
goes red the PR just sits open and waits for you. There is no AI in this loop —
an earlier "ambient agent" design was removed because the test suite already
encodes the merge gate.

## One-time setup: `RELEASE_PAT`

GitHub deliberately stops the built-in `GITHUB_TOKEN` from driving a release:
a PR opened by `GITHUB_TOKEN` does **not** trigger CI, and a merge performed by
it does **not** fire the post-merge tag/publish chain (the anti-recursion rule).
So the bot needs to act as a real identity — one free, long-lived token does it.

1. Create a **fine-grained personal access token**
(GitHub → Settings → Developer settings → Fine-grained tokens):
- **Repository access:** only `cache-components-cache-handler`
- **Expiration:** the longest available (or no expiration)
- **Permissions:** **Contents: Read and write** + **Pull requests: Read and write**
(nothing else — no admin, no workflows)

2. Add it as a repo secret named **`RELEASE_PAT`**:
```bash
gh secret set RELEASE_PAT
```

That's it. It costs nothing and there is no second token to rotate (npm publish
uses OIDC trusted publishing — see [publishing](./publishing/AUTOMATED_RELEASE.md)).

### Without the PAT

The loop degrades gracefully: `nextjs-version-check` still opens the bump PR, but
auto-merge stays off and the workflow logs a warning. Merge the PR yourself once
CI is green — the tag and publish still fire automatically from your merge.

## Branch protection

`main` requires the status checks `lint-and-typecheck`, `unit-tests`, and
`test-summary`, and does **not** require an approving review — the checks are the
gate, and no bot can give itself an approval. Required conversation resolution
stays on (only matters when a human leaves review comments). Auto-merge and
delete-branch-on-merge are enabled at the repo level.

To re-add a human approval gate later:

```bash
gh api -X PUT repos/{owner}/{repo}/branches/main/protection --input - <<'JSON'
{ "required_status_checks": { "strict": false,
"contexts": ["lint-and-typecheck", "unit-tests", "test-summary"] },
"enforce_admins": false,
"required_pull_request_reviews": { "required_approving_review_count": 1 },
"restrictions": null, "required_conversation_resolution": true }
JSON
Comment on lines +64 to +70

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The GitHub REST API for updating branch protection (PUT /repos/{owner}/{repo}/branches/{branch}/protection) is strict about the required_pull_request_reviews object. If you omit dismiss_stale_reviews and require_code_owner_reviews, the API call will fail with a 422 Unprocessable Entity validation error. Adding these fields with default boolean values ensures the command runs successfully.

Suggested change
gh api -X PUT repos/{owner}/{repo}/branches/main/protection --input - <<'JSON'
{ "required_status_checks": { "strict": false,
"contexts": ["lint-and-typecheck", "unit-tests", "test-summary"] },
"enforce_admins": false,
"required_pull_request_reviews": { "required_approving_review_count": 1 },
"restrictions": null, "required_conversation_resolution": true }
JSON
gh api -X PUT repos/{owner}/{repo}/branches/main/protection --input - <<'JSON'
{ "required_status_checks": { "strict": false,
"contexts": ["lint-and-typecheck", "unit-tests", "test-summary"] },
"enforce_admins": false,
"required_pull_request_reviews": {
"dismiss_stale_reviews": false,
"require_code_owner_reviews": false,
"required_approving_review_count": 1
},
"restrictions": null, "required_conversation_resolution": true }
JSON

```
Loading