Skip to content

ci(pr-preview): register dynamic GitHub deployment per PR#2

Merged
isuttell merged 3 commits into
mainfrom
t3code/638b75b9
May 21, 2026
Merged

ci(pr-preview): register dynamic GitHub deployment per PR#2
isuttell merged 3 commits into
mainfrom
t3code/638b75b9

Conversation

@isuttell

@isuttell isuttell commented May 21, 2026

Copy link
Copy Markdown
Contributor

Summary

Make GitHub show a Deployments panel on each PR when the PR preview workflow runs. Today the preview deploys to ephemeral *-pr-N Workers but GitHub has no record, so PRs don't surface the URL or status. A dynamic per-PR environment is created on deploy and deleted on PR close, so the environment exists iff the Workers exist.

Changes

  • .github/workflows/pr-preview.yml: declare environment: pr-preview-<N> with url: ${{ steps.deploy.outputs.apex_url }} on the deploy job. GitHub auto-creates the env on first run.
  • .github/workflows/cleanup-pr-preview.yml: add administration: write to the job permissions and a new step that calls DELETE /repos/{owner}/{repo}/environments/{name} via actions/github-script. 404 is treated as a no-op so cleanups for never-deployed PRs (forks, failed setup) don't fail.

Risk: LOW

  • Areas touched: GitHub Actions workflows only. No application code, no migrations.
  • Security: cleanup job now requests administration: write on the auto-issued GITHUB_TOKEN. Scope is needed to delete environments; not used for anything else. If org policy denies the elevation, the cleanup step 403s but the workers/Neon branch already deleted before that point.
  • Performance: none.
  • Breaking: no. Existing Preview environment (empty, manually created) is left in place for a future long-lived preview deploy workflow.

Test plan

  • Open this PR — confirm GitHub creates a pr-preview-<this-PR-number> environment, the deploy job runs against it, and the PR sidebar shows a Deployments panel with the apex URL.
  • Close the PR — confirm the cleanup workflow runs, deletes the Cloudflare/Neon resources, and removes the pr-preview-<N> environment from gh api repos/zaks-io/agent-paste/environments.
  • Re-open the PR — confirm a new environment is created and the cycle works again.

CodeRabbit notes

  • Skipped: pin actions/github-script@v8 to a commit SHA. Conflicts with the repo convention of floating major tags across all actions/* actions (see ci.yml, pr-preview.yml, and the existing comment step in this same file).

Summary by CodeRabbit

  • Chores

    • Improved PR preview lifecycle: added safer cleanup, explicit preview environment declaration, and post-deploy DNS/availability checks; updated token permissions for these steps.
  • Documentation

    • Centralized project status and roadmap into a single ops document (replacing the old bootstrap checklist); added onboarding order guidance and updated MVP spec to reference the new source of truth.

Review Change Stack

isuttell and others added 2 commits May 21, 2026 13:55
Reframe the MVP-only bootstrap checklist as a project-wide status doc
covering all six roadmap phases. Adds spec coverage (15 specs), ADR
coverage (66 ADRs with status + gap), phase status, and an ordered
15-item backlog with verifiable Done criteria so a fresh session can
pick up "the next step" without rediscovering scope.

Wires AGENTS.md to point at it as the first read.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add `environment: pr-preview-<N>` to the PR preview deploy job so GitHub
surfaces a Deployments panel on the PR with the apex URL. Cleanup workflow
deletes the environment on PR close so it exists iff the workers exist;
this needs `administration: write` on the cleanup job.
@coderabbitai

coderabbitai Bot commented May 21, 2026

Copy link
Copy Markdown

Walkthrough

This PR implements automated cleanup for GitHub Actions PR preview environments and consolidates project status documentation. The pr-preview workflow now tags deployments with named GitHub Actions environments, enabling environment-based deployment protection and tracking. A new cleanup-pr-preview workflow acquires administrative permissions and deletes these per-PR environments when PRs close, gracefully handling missing environments. Concurrently, project-status.md becomes the single source of truth for project roadmap status, implementation state, verified testing results, specification coverage gaps, ADR tracking, and a prioritized fifteen-item backlog. The old mvp-bootstrap-checklist.md is removed, and documentation references are updated to point to the new home.

Sequence Diagram(s)

sequenceDiagram
  participant ComponentA
  participant ComponentB
  ComponentA->>ComponentB: observable interaction
Loading

Possibly related PRs

  • zaks-io/agent-paste#1: Also modifies pr-preview.yml to surface the Apex URL and adds preview wait handling related to that endpoint.

Poem

🐰 Environments rise on code's bright wing,

URLs checked until they sing,
When PRs close, the tidy sweep begins,
One doc to read, no scattered bins,
A rabbit hums — deploys end with grins.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the primary change: registering a dynamic GitHub deployment environment per PR in the CI/preview workflow. It clearly identifies the main objective and scope of the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch t3code/638b75b9

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/cleanup-pr-preview.yml:
- Line 16: Remove the invalid permission line "administration: write" and
instead mint a GitHub App installation token using the
actions/create-github-app-token@v2 action, then pass that token into the
environment deletion step via the github-token input; update the job to call
create-github-app-token (store its output as e.g. github_app_token) and use that
output in the deleteAnEnvironment call so the repository Administration write
scope is provided by the App token rather than GITHUB_TOKEN.

In `@AGENTS.md`:
- Around line 3-8: Update AGENTS.md to add explicit agent definitions and
configuration fields: for each agent (give them clear identifiers/names),
document role, capabilities, constraints, required inputs and environment
variables, ownership/escalation path, expected outputs, and any runtime/config
flags; derive implemented/partial/deferred status from
docs/ops/project-status.md and cross-reference relevant domain language in
CONTEXT.md and specs in docs/specs/README.md; ensure each agent section uses
consistent field names (e.g., name, role, capabilities, constraints,
required_env, escalation_owner, status) so downstream code can parse these
entries.

In `@docs/ops/project-status.md`:
- Around line 166-236: MD022 errors come from missing blank lines immediately
after level-3 backlog headings; edit the Markdown document and insert a single
blank line after each "###" backlog heading (e.g., "### 1. Wire `runCommand` and
`createOperationEvent` into mutation routes", "### 2. Enforce native rate-limit
bindings", etc.) so every level-3 heading is followed by exactly one empty line
before the next paragraph or list item, then run markdownlint to verify the
MD022 warnings are resolved.
- Line 5: The docs conflict: docs/ops/project-status.md currently states it is
read after CONTEXT.md/specs/ADRs while AGENTS.md says to read project-status.md
first; pick the canonical read order and make the wording consistent across both
files—either change the sentence in project-status.md that begins "First doc a
fresh agent reads after ..." to match AGENTS.md's “read first” phrasing, or
update AGENTS.md to instruct reading it after
CONTEXT.md/docs/specs/README.md/docs/adr/README.md; ensure the doc titles
(project-status.md, AGENTS.md, CONTEXT.md, docs/specs/README.md,
docs/adr/README.md) are referenced consistently and update the single line where
the order is described.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 2850c318-c228-4dbf-8073-4a1c777c6785

📥 Commits

Reviewing files that changed from the base of the PR and between 4bde837 and b41136f.

📒 Files selected for processing (6)
  • .github/workflows/cleanup-pr-preview.yml
  • .github/workflows/pr-preview.yml
  • AGENTS.md
  • docs/ops/mvp-bootstrap-checklist.md
  • docs/ops/project-status.md
  • docs/specs/mvp.md
💤 Files with no reviewable changes (1)
  • docs/ops/mvp-bootstrap-checklist.md

permissions:
contents: read
pull-requests: write
administration: write

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

cat -n .github/workflows/cleanup-pr-preview.yml | head -100

Repository: zaks-io/agent-paste

Length of output: 3768


🌐 Web query:

GitHub Actions GITHUB_TOKEN workflow permissions available scopes list

💡 Result:

GitHub Actions’ automatically created GITHUB_TOKEN supports these workflow/job “permissions” scopes (parameterized names) that you can set via the permissions: key (values typically read/write/none depending on the scope): actions, checks, contents, deployments, issues, packages, pull-requests, repository-projects, security-events, statuses. [1][2][3] Note: The scopes list is used with the workflow permissions key to grant least privilege; any permission that is absent is set to none. [1] Example: permissions: contents: read packages: read pull-requests: none [1]

Citations:


🌐 Web query:

GitHub REST API deleteAnEnvironment authorization requirements permissions

💡 Result:

To delete an environment using the GitHub REST API (DELETE /repos/{owner}/{repo}/environments/{environment_name}), you must meet specific authorization and permission requirements depending on the authentication method used [1][2][3]. Personal Access Tokens (PATs) For personal access tokens (classic), the token must have the 'repo' scope to successfully call this endpoint [1][2][3]. Fine-grained Access Tokens and GitHub Apps For fine-grained personal access tokens, as well as GitHub App user access tokens and installation access tokens, the token must be granted the 'Administration' repository permission with 'write' access [3]. General Authentication Notes This endpoint is available for GitHub Apps (user-to-server and installation-to-server) and fine-grained personal access tokens, provided they carry the required 'Administration' write permissions [3]. Ensure that the {environment_name} is properly URL-encoded if it contains special characters such as slashes (e.g., using %2F) [1][2][3]. If you encounter a 404 error despite having correct permissions, verify that the environment name is correctly encoded and that the environment exists within the specified repository [4].

Citations:


The cleanup workflow cannot authorize the environment deletion API call.

administration is not a valid GITHUB_TOKEN workflow permission scope. GitHub Actions workflows support only: actions, checks, contents, deployments, issues, packages, pull-requests, repository-projects, security-events, and statuses. Line 16 cannot grant the access the delete call requires—the deleteAnEnvironment endpoint requires repository Administration write permission, which is not available to GITHUB_TOKEN. This workflow will fail at the deletion step.

Mint a GitHub App installation token via actions/create-github-app-token@v2 and pass it to the deletion step with github-token:, then remove line 16.

Suggested shape
 permissions:
   contents: read
   pull-requests: write
-  administration: write

+      - name: Mint repo-admin token
+        id: app-token
+        uses: actions/create-github-app-token@v2
+        with:
+          app-id: ${{ vars.REPO_ADMIN_APP_ID }}
+          private-key: ${{ secrets.REPO_ADMIN_APP_PRIVATE_KEY }}
+
       - name: Delete GitHub Preview environment
         uses: actions/github-script@v8
         with:
+          github-token: ${{ steps.app-token.outputs.token }}
           script: |
             const name = `pr-preview-${process.env.PR_NUMBER}`;
             try {
               await github.rest.repos.deleteAnEnvironment({
🧰 Tools
🪛 actionlint (1.7.12)

[error] 16-16: unknown permission scope "administration". all available permission scopes are "actions", "artifact-metadata", "attestations", "checks", "contents", "deployments", "discussions", "id-token", "issues", "models", "packages", "pages", "pull-requests", "repository-projects", "security-events", "statuses"

(permissions)

🪛 zizmor (1.25.2)

[warning] 16-16: overly broad permissions (excessive-permissions): administration: write is overly broad at the workflow level

(excessive-permissions)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/cleanup-pr-preview.yml at line 16, Remove the invalid
permission line "administration: write" and instead mint a GitHub App
installation token using the actions/create-github-app-token@v2 action, then
pass that token into the environment deletion step via the github-token input;
update the job to call create-github-app-token (store its output as e.g.
github_app_token) and use that output in the deleteAnEnvironment call so the
repository Administration write scope is provided by the App token rather than
GITHUB_TOKEN.

Comment thread AGENTS.md
Comment on lines +3 to +8
## Start here

Read `docs/ops/project-status.md` first. It is the source of truth for what is implemented, what is partial, what is deferred, and the ordered backlog. When asked to "implement the next step," start at the top of the backlog in that file.

Then: `CONTEXT.md` (domain language), `docs/specs/README.md` (spec reading order), `docs/adr/README.md` (ADR index).

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Add explicit agent definitions and config fields to AGENTS.md.

This section defines reading order only; it still does not document concrete agent definitions and their configurations (roles, capabilities, constraints, required inputs/env, escalation/ownership), which is required for this file.

As per coding guidelines AGENTS.md: "AGENTS.md file should document agent definitions and their configurations".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@AGENTS.md` around lines 3 - 8, Update AGENTS.md to add explicit agent
definitions and configuration fields: for each agent (give them clear
identifiers/names), document role, capabilities, constraints, required inputs
and environment variables, ownership/escalation path, expected outputs, and any
runtime/config flags; derive implemented/partial/deferred status from
docs/ops/project-status.md and cross-reference relevant domain language in
CONTEXT.md and specs in docs/specs/README.md; ensure each agent section uses
consistent field names (e.g., name, role, capabilities, constraints,
required_env, escalation_owner, status) so downstream code can parse these
entries.


Last updated: 2026-05-21.

First doc a fresh agent reads after `AGENTS.md`, `CONTEXT.md`, `docs/specs/README.md`, and `docs/adr/README.md`. Answers: what is built, what is scaffolded, where the code diverges from the ADRs/specs, what the next concrete step is.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Align read-order wording with AGENTS.md to avoid contradictory onboarding guidance.

Line 5 says this doc is read after CONTEXT.md/specs/ADRs, while AGENTS.md now instructs reading this doc first. Keep one canonical order across both docs.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/ops/project-status.md` at line 5, The docs conflict:
docs/ops/project-status.md currently states it is read after
CONTEXT.md/specs/ADRs while AGENTS.md says to read project-status.md first; pick
the canonical read order and make the wording consistent across both
files—either change the sentence in project-status.md that begins "First doc a
fresh agent reads after ..." to match AGENTS.md's “read first” phrasing, or
update AGENTS.md to instruct reading it after
CONTEXT.md/docs/specs/README.md/docs/adr/README.md; ensure the doc titles
(project-status.md, AGENTS.md, CONTEXT.md, docs/specs/README.md,
docs/adr/README.md) are referenced consistently and update the single line where
the order is described.

Comment on lines +166 to +236
### 1. Wire `runCommand` and `createOperationEvent` into mutation routes
- Drives: ADR 0022, ADR 0035, `docs/specs/api.md`
- Files: `apps/api/src/index.ts`, `apps/upload/src/index.ts`, `packages/commands/src/index.ts`
- Done: every POST/PUT/DELETE route in `api` and `upload` is wrapped via `runCommand`; idempotency keys are honored from `Idempotency-Key` header; replay of the same idempotency key returns the original result; vitest covers replay for at least workspace-create, api-key-create, artifact-delete.

### 2. Enforce native rate-limit bindings
- Drives: ADR 0039, ADR 0064
- Files: `apps/api/src/index.ts`, `apps/upload/src/index.ts`, `apps/*/wrangler.jsonc`
- Done: every authenticated mutation calls `env.ACTOR_RATE_LIMIT.limit(...)` and `env.WORKSPACE_BURST_CAP.limit(...)`; over-limit returns 429 with envelope; a smoke test triggers 429 from an in-test client.

### 3. Generate OpenAPI from Zod contracts
- Drives: ADR 0016, ADR 0017, ADR 0038
- Files: `packages/contracts/src/*`, `apps/api/src/index.ts`, `apps/upload/src/index.ts`, `apps/content/src/index.ts`
- Done: `/openapi.json` on api/upload/content is generated from `packages/contracts` via `@hono/zod-openapi` (or equivalent); `pnpm verify` runs a schema-diff check against a checked-in golden; CI fails if contracts drift from served OpenAPI.

### 4. Add `--yes` guards to destructive admin CLI commands
- Drives: `docs/specs/admin.md`
- Files: `apps/cli/src/index.ts`
- Done: `agent-paste admin api-key revoke`, `artifacts delete`, and `cleanup run` (non-dry-run) refuse to run without `--yes`; CLI test asserts refusal and a `--yes` happy path.

### 5. Consolidate content-signing secret names
- Drives: ADR 0028, ADR 0058
- Files: `scripts/bootstrap-secrets.mjs`, `apps/*/wrangler.jsonc`, `apps/api/src/index.ts`, `apps/content/src/index.ts`
- Done: only one of `CONTENT_SIGNING_SECRET` / `CONTENT_GATEWAY_SIGNING_KEY_V1` remains, named consistently across code, bootstrap script, and ADRs; a one-time rotation note is added to `docs/ops/runbook` (or this doc) for any environment that already holds both.

### 6. Move runtime queries to Drizzle
- Drives: ADR 0018
- Files: `packages/db/src/**`, callers in `apps/api`, `apps/upload`
- Done: workspace/api-key/artifact/upload-session reads and writes flow through Drizzle query objects (not raw SQL templates); `pnpm verify` runs a Drizzle introspection check against the migration file. Scope this to MVP routes; leave admin/cleanup queries as a follow-up if the change balloons.

### 7. Apply Postgres RLS at runtime
- Drives: ADR 0044
- Files: `packages/db/src/**`, `apps/api/src/index.ts`, `apps/upload/src/index.ts`, `packages/db/migrations/*`
- Done: Hyperdrive role is `NOBYPASSRLS`; every request opens a Postgres txn that issues `SET LOCAL app.workspace_id = $1` before any query; a vitest scenario inserts two workspaces and confirms cross-workspace reads return zero rows.

### 8. Complete error envelope (`request_id`, `docs`)
- Drives: ADR 0036, `docs/specs/contracts.md`
- Files: `apps/api/src/index.ts`, `apps/upload/src/index.ts`, `apps/content/src/index.ts`, `packages/contracts/src/*`
- Done: every error response includes `request_id`; an optional `docs` URL is attached for codes that have a documented remediation; `X-Request-Id` header is echoed on every response (error or success); golden tests cover at least 404/401/409/422/429/500.

### 9. Verify bytes-after-delete and bytes-after-expiry cleanup
- Drives: ADR 0048, `docs/specs/acceptance.md`
- Files: `apps/api/src/index.ts` (scheduled handler), `scripts/smoke-local-mvp.mjs`, `scripts/smoke-hosted.mjs`
- Done: smoke creates an artifact with a 1-day TTL, advances clock (or uses a forced-expiry test endpoint), runs cleanup, confirms R2 prefix is empty and signed URL returns 404 with denylist hit logged.

### 10. Exercise PR preview lifecycle on a same-repo PR
- Drives: ADR 0007, ADR 0012, `.github/workflows/pr-preview.yml`
- Files: workflow itself, `scripts/deploy-pr-preview.mjs`, `scripts/cleanup-pr-preview.mjs`
- Done: a same-repo PR (the one carrying items 1-9 above is the natural candidate) creates a Neon branch, deploys preview Workers, runs hosted smoke, posts a comment with URLs, and tears everything down on close. Captured run links recorded in this doc.

### 11. Wire Logpush → Axiom for `api`/`upload`/`content`
- Drives: ADR 0011, `docs/specs/phases.md` Phase 2
- Files: Cloudflare console + `docs/ops/` runbook (no Worker code change required if using Cloudflare Logs config)
- Done: an Axiom dataset receives Worker logs for all three Workers; a basic dashboard shows 5xx rate and p95 latency; secrets/PII redaction confirmed (no API key secret or signed-URL token in logs).

### 12. Review and merge `t3code/7bcd4587`
- Drives: this branch holds Apex/front-end and CI work that needs to land or be discarded.
- Files: TBD until review.
- Done: branch is either merged to `main` (with conflicts resolved and CI green) or closed with a written reason. Same decision for `t3code/5b6355f9` if still extant.

### 13. Decide on remaining ADR 0046 drift (operator identity)
- Drives: ADR 0046
- Files: ADR 0046 itself, possibly a new ADR
- Done: ADR 0046 is amended (or a new ADR supersedes it) to either accept the single-bearer admin token as the production model or to commit Cloudflare Access + email allowlist before Phase 3. No code change in this item — just a written decision.

### 14. CSP allowlist audit
- Drives: ADR 0029, ADR 0030, `docs/specs/content-rendering.md`
- Files: `apps/content/src/index.ts`
- Done: CSP `script-src` and `connect-src` allowlists are validated against the current ADR 0029 list; SVG responses use a strict CSP override; a vitest snapshot pins the CSP header for HTML, CSS, JS, SVG, and PNG.

### 15. Complete bootstrap hosting checklist

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix markdownlint MD022 violations on backlog subsection headings.

Each ### backlog item heading needs a blank line after the heading. The current layout triggers the reported MD022 warnings.

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 166-166: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 171-171: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 176-176: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 181-181: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 186-186: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 191-191: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 196-196: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 201-201: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 206-206: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 211-211: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 216-216: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 221-221: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 226-226: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 231-231: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)


[warning] 236-236: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below

(MD022, blanks-around-headings)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/ops/project-status.md` around lines 166 - 236, MD022 errors come from
missing blank lines immediately after level-3 backlog headings; edit the
Markdown document and insert a single blank line after each "###" backlog
heading (e.g., "### 1. Wire `runCommand` and `createOperationEvent` into
mutation routes", "### 2. Enforce native rate-limit bindings", etc.) so every
level-3 heading is followed by exactly one empty line before the next paragraph
or list item, then run markdownlint to verify the MD022 warnings are resolved.

The smoke step was hitting ETIMEDOUT against the freshly deployed
`*.workers.dev` URLs because Cloudflare propagation lags the deploy by
several seconds on first publish. Poll each worker URL with a 5s timeout
up to 30 times before running smoke.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/pr-preview.yml:
- Around line 110-119: The timeout message "did not become reachable within 60s"
is incorrect for the retry loop using i, curl --max-time 5 and sleep 2 across 30
attempts (worst-case ~210s); update the error reporting to reflect the actual
wait window by either changing the static message to the correct value (e.g.,
"~210s") or compute and report the real elapsed time using shell SECONDS (set
SECONDS=0 before the loop and include $SECONDS in the ::error:: message) so the
failure log accurately shows how long the script waited.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: ebfcf8f3-ccfb-4fd5-978a-3ad51143ef82

📥 Commits

Reviewing files that changed from the base of the PR and between b41136f and f43d07a.

📒 Files selected for processing (1)
  • .github/workflows/pr-preview.yml

Comment on lines +110 to +119
while [ $i -lt 30 ]; do
i=$((i+1))
if curl -sS -o /dev/null --max-time 5 "$url"; then
echo "$url reachable after $i attempt(s)"
return 0
fi
sleep 2
done
echo "::error::$url did not become reachable within 60s"
return 1

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Timeout message is inconsistent with the implemented retry window.

The loop can run for ~210 seconds, but Line 118 reports a 60-second timeout. This makes failures harder to interpret during CI incidents.

Suggested fix
           wait_for() {
             local url="$1"
+            local attempts=30
+            local curl_timeout=5
+            local sleep_seconds=2
+            local max_wait=$((attempts * (curl_timeout + sleep_seconds)))
             local i=0
-            while [ $i -lt 30 ]; do
+            while [ $i -lt "$attempts" ]; do
               i=$((i+1))
-              if curl -sS -o /dev/null --max-time 5 "$url"; then
+              if curl -sS -o /dev/null --max-time "$curl_timeout" "$url"; then
                 echo "$url reachable after $i attempt(s)"
                 return 0
               fi
-              sleep 2
+              sleep "$sleep_seconds"
             done
-            echo "::error::$url did not become reachable within 60s"
+            echo "::error::$url did not become reachable within ${max_wait}s"
             return 1
           }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/pr-preview.yml around lines 110 - 119, The timeout message
"did not become reachable within 60s" is incorrect for the retry loop using i,
curl --max-time 5 and sleep 2 across 30 attempts (worst-case ~210s); update the
error reporting to reflect the actual wait window by either changing the static
message to the correct value (e.g., "~210s") or compute and report the real
elapsed time using shell SECONDS (set SECONDS=0 before the loop and include
$SECONDS in the ::error:: message) so the failure log accurately shows how long
the script waited.

@isuttell isuttell merged commit ab363cc into main May 21, 2026
4 checks passed
@isuttell isuttell deleted the t3code/638b75b9 branch May 22, 2026 01:02
isuttell added a commit that referenced this pull request May 22, 2026
PR #9 finished the rate-limit work, so mark ADR 0039/0064 done, drop
the backlog entry, and shift items #2-#11 down one slot. Updates the
phase partition note to match the new numbering.
isuttell added a commit that referenced this pull request May 22, 2026
…ting (#9)

* fix(api,upload): replay idempotent mutations before rate-limit accounting

Adds a SELECT-only peekIdempotentReplay helper to @agent-paste/commands
and uses it in both upload mutation routes to short-circuit known-good
retries before either rate-limit binding is touched. Hosted smoke now
hammers POST /v1/upload-sessions with unique idempotency keys to confirm
429 with the rate_limited_actor envelope on preview targets.

* docs(status): close out item #1 and renumber backlog

PR #9 finished the rate-limit work, so mark ADR 0039/0064 done, drop
the backlog entry, and shift items #2-#11 down one slot. Updates the
phase partition note to match the new numbering.

* debug: dump content fetch context on failure

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore: drop temporary debug print from smoke-hosted

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(smoke): run rate-limit probe after purge tests; reconcile backlog

The new assertActorRateLimitFires helper exhausts the actor's 60/min
budget by hammering an upload mutation. The next assertion,
assertBytesPurgedAfterExpiry, republishes through the same user API key
and was hitting rate_limited_actor at the publish step.

Reorder so the rate-limit probe runs last; bytes-after-delete and
-after-expiry now run on a fresh budget.

Also reconcile docs/ops/project-status.md: PR #6 (CSP) and PR #8
(bytes-after-delete/expiry) closed two backlog items without updating
this doc. Moved both into Recently Completed and renumbered.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(smoke): fire rate-limit probe in parallel waves

Sequential 120 attempts at ~2/sec spread across multiple Cloudflare
worker isolates means no single isolate ever sees the 60-in-60s
threshold. Replace the serial loop with four parallel waves of 80
requests each. The burst forces a single isolate's counter past the
limit before the trailing window slides.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
isuttell added a commit that referenced this pull request May 22, 2026
PR #2 added `permissions: administration: write` to the cleanup
workflow, but `administration` is not a valid GITHUB_TOKEN scope. GitHub
rejected the workflow at evaluation time, silently dropping every
`pull_request.closed` event for PRs #2--#9 and accumulating eight
stale `preview/pr-N` Neon branches that tripped the 10-branch free-tier
cap (blocks PR #10/#11/#12 deploys with HTTP 422).

Drop the invalid permission and the `deleteAnEnvironment` step that
required it; rename the file so GitHub registers a fresh workflow id
instead of reusing the wedged record; validate the resolved PR number
is a positive integer before deleting anything.

Stale Neon branches still need a one-time operator purge -- agent is
forbidden from calling `neondatabase/delete-branch-action` autonomously.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
isuttell added a commit that referenced this pull request May 22, 2026
PR #2 added `permissions: administration: write` to the cleanup
workflow, but `administration` is not a valid GITHUB_TOKEN scope. GitHub
rejected the workflow at evaluation time, silently dropping every
`pull_request.closed` event for PRs #2--#9 and accumulating eight
stale `preview/pr-N` Neon branches that tripped the 10-branch free-tier
cap (blocks PR #10/#11/#12 deploys with HTTP 422).

Drop the invalid permission and the `deleteAnEnvironment` step that
required it; rename the file so GitHub registers a fresh workflow id
instead of reusing the wedged record; validate the resolved PR number
is a positive integer before deleting anything.

Stale Neon branches still need a one-time operator purge -- agent is
forbidden from calling `neondatabase/delete-branch-action` autonomously.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
isuttell added a commit that referenced this pull request May 24, 2026
* feat: add PATCH /v1/web/settings with per-workspace retention

Backlog item #2 (Phase 3 dashboard). Persist auto-deletion retention
per workspace and let admins edit workspace name + retention from the
dashboard.

- workspaces gains auto_deletion_days (int, default 30, CHECK 1..90) via
  migration 0007; schema + snapshot updated.
- GET /v1/web/settings now reads the persisted value instead of the
  global USAGE_POLICY default.
- PATCH /v1/web/settings (workos_access_token, admin scope, idempotent,
  actor rate limit) updates name + retention through runCommand and
  writes a workspace.settings.updated audit event. Bounds (1..90 days,
  name 1..120) enforced at the Zod contract and the DB check constraint.
- Repo: workspaces.update port + both adapters; updateWebSettings in
  RepositoryCore; toWebSettings dedups the GET/PATCH shape.
- Contract + OpenAPI golden regenerated; api + db tests extended.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix: fail closed on out-of-range retention in repository core

Address CodeRabbit review on #44. The local adapter has no DB CHECK
constraint, so a direct updateWebSettings call could persist an
auto_deletion_days value Postgres would reject. Guard against it in
RepositoryCore using the shared MIN/MAX_AUTO_DELETION_DAYS bounds and
lock the behavior with a repo test.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant