Skip to content

feat: add PR preview deployments via Cloudflare Pages#302

Merged
Aureliolo merged 18 commits intomainfrom
feat/pr-preview-deployments
Mar 11, 2026
Merged

feat: add PR preview deployments via Cloudflare Pages#302
Aureliolo merged 18 commits intomainfrom
feat/pr-preview-deployments

Conversation

@Aureliolo
Copy link
Copy Markdown
Owner

Summary

  • New workflow: .github/workflows/pages-preview.yml — builds MkDocs + Astro on PRs, injects "Development Preview" banner, deploys to Cloudflare Pages
  • Each PR gets a unique preview URL at pr-<number>.synthorg-pr-preview.pages.dev
  • Preview URL auto-commented on the PR (paginated comment search, updates existing comment on re-push)
  • Security hardened: env vars for expression injection mitigation, fork PR guard, SHA-pinned actions, persist-credentials: false, least-privilege permissions
  • Bug fixes: 3 broken links in docs/getting_started.md (MkDocs --strict), docstring indentation in messenger.py (griffe warning)
  • Docs: CLAUDE.md CI section updated with PR Preview details

Review feedback incorporated

All valid findings from PR #300 reviewers (Gemini, Greptile, Copilot) addressed:

  • Concurrency group with cancel-in-progress (Greptile + Copilot)
  • Paginated listComments to avoid missing existing comment (Copilot)
  • PR head SHA instead of merge commit SHA in comment (Copilot)
  • Removed unused deployments: write permission (Copilot)
  • Wrangler run step instead of action (fixes env var expansion bug)
  • PR number in URL instead of branch slug (cleaner, shorter)

Dismissed: absolute links to main in getting_started.md (intentional — files outside docs/ hierarchy)

Test plan

  • Verify workflow triggers on PR with docs/site/mkdocs changes
  • Verify MkDocs + Astro build succeeds
  • Verify preview banner appears on all HTML pages
  • Verify Cloudflare Pages deployment succeeds
  • Verify preview URL comment appears on PR with correct SHA
  • Verify concurrency cancels stale builds on rapid push
  • Verify fork PRs skip deploy job gracefully

🤖 Generated with Claude Code

- Add pages-preview.yml workflow that builds site on PRs and deploys
  to Cloudflare Pages with a "Development Preview" banner injected
  into all HTML pages. Each PR gets a unique preview URL posted as
  a comment.
- Fix MkDocs strict-mode failures: replace 3 broken relative links
  in getting_started.md with GitHub URLs, fix docstring indentation
  in messenger.py (griffe warning).
- Document PR Preview workflow in CLAUDE.md.
Pre-reviewed by 7 agents, 7 findings addressed:
- Fix expression injection via github.head_ref (use env var)
- Fix expression injection via deployment-url output (use process.env)
- Add fork PR guard to skip deploy when secrets unavailable
- Remove unused artifact-uploaded output
- Use lambda in re.sub to prevent backslash misfire
- Port inline comments from pages.yml for consistency
- Fix CLAUDE.md wording about deploy job behavior
- Add concurrency group to cancel stale preview builds on rapid pushes
- Paginate listComments to avoid missing existing preview comment
- Use PR head SHA instead of merge commit SHA in preview comment
- Rename project to synthorg-pr-preview
- Use PR number in preview URL instead of branch name
- Replace wrangler-action with run step (fixes env var expansion)
- Remove unused deployments:write permission
- Update CLAUDE.md with new project name and concurrency docs
Copilot AI review requested due to automatic review settings March 11, 2026 17:47
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 11, 2026

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

OpenSSF Scorecard

PackageVersionScoreDetails
actions/actions/checkout de0fac2e4500dabe0009e67214ff5f5447ce83dd 🟢 5.9
Details
CheckScoreReason
Maintained⚠️ 23 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 2
Code-Review🟢 10all changesets reviewed
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Binary-Artifacts🟢 10no binaries found in the repo
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
Packaging⚠️ -1packaging workflow not detected
Fuzzing⚠️ 0project is not fuzzed
License🟢 10license file detected
Signed-Releases⚠️ -1no releases found
Pinned-Dependencies🟢 3dependency not pinned by hash detected -- score normalized to 3
Security-Policy🟢 9security policy file detected
Branch-Protection🟢 5branch protection is not maximal on development and all release branches
SAST🟢 8SAST tool detected but not run on all commits
actions/actions/download-artifact 3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c 🟢 6.2
Details
CheckScoreReason
Code-Review🟢 10all changesets reviewed
Packaging⚠️ -1packaging workflow not detected
Maintained🟢 1030 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10
Binary-Artifacts🟢 10no binaries found in the repo
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Pinned-Dependencies⚠️ 1dependency not pinned by hash detected -- score normalized to 1
License🟢 10license file detected
Fuzzing⚠️ 0project is not fuzzed
Signed-Releases⚠️ -1no releases found
Security-Policy🟢 9security policy file detected
Branch-Protection⚠️ 0branch protection not enabled on development/release branches
SAST🟢 10SAST tool is run on all commits
actions/actions/github-script ed597411d8f924073f98dfc5c65a23a2325f34cd 🟢 7.7
Details
CheckScoreReason
Packaging⚠️ -1packaging workflow not detected
Code-Review🟢 10all changesets reviewed
Binary-Artifacts🟢 10no binaries found in the repo
Maintained🟢 1013 commit(s) and 1 issue activity found in the last 90 days -- score normalized to 10
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Token-Permissions🟢 9detected GitHub workflow tokens with excessive permissions
Pinned-Dependencies⚠️ 1dependency not pinned by hash detected -- score normalized to 1
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
License🟢 10license file detected
Fuzzing⚠️ 0project is not fuzzed
Signed-Releases⚠️ -1no releases found
Security-Policy🟢 9security policy file detected
Branch-Protection🟢 5branch protection is not maximal on development and all release branches
SAST🟢 10SAST tool is run on all commits
actions/actions/setup-node 49933ea5288caeca8642d1e84afbd3f7d6820020 🟢 6.1
Details
CheckScoreReason
Maintained🟢 1012 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10
Code-Review🟢 10all changesets reviewed
Binary-Artifacts🟢 9binaries present in source code
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Packaging⚠️ -1packaging workflow not detected
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
Pinned-Dependencies⚠️ 0dependency not pinned by hash detected -- score normalized to 0
License🟢 10license file detected
Fuzzing⚠️ 0project is not fuzzed
Signed-Releases⚠️ -1no releases found
Security-Policy🟢 9security policy file detected
Branch-Protection⚠️ 1branch protection is not maximal on development and all release branches
SAST🟢 9SAST tool is not run on all commits -- score normalized to 9
actions/actions/setup-python a26af69be951a213d495a4c3e4e4022e16d87065 🟢 5.3
Details
CheckScoreReason
Maintained🟢 34 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 3
Code-Review🟢 10all changesets reviewed
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Binary-Artifacts🟢 10no binaries found in the repo
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Packaging⚠️ -1packaging workflow not detected
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
Pinned-Dependencies⚠️ 0dependency not pinned by hash detected -- score normalized to 0
Fuzzing⚠️ 0project is not fuzzed
License🟢 10license file detected
Signed-Releases⚠️ -1no releases found
Security-Policy🟢 9security policy file detected
Branch-Protection⚠️ 0branch protection not enabled on development/release branches
SAST🟢 9SAST tool is not run on all commits -- score normalized to 9
actions/actions/upload-artifact bbbca2ddaa5d8feaa63e36b76fdaad77386f024f 🟢 6.2
Details
CheckScoreReason
Maintained🟢 1028 commit(s) and 1 issue activity found in the last 90 days -- score normalized to 10
Binary-Artifacts🟢 10no binaries found in the repo
Code-Review🟢 10all changesets reviewed
Dangerous-Workflow🟢 10no dangerous workflow patterns detected
Packaging⚠️ -1packaging workflow not detected
CII-Best-Practices⚠️ 0no effort to earn an OpenSSF best practices badge detected
Token-Permissions⚠️ 0detected GitHub workflow tokens with excessive permissions
Pinned-Dependencies⚠️ 1dependency not pinned by hash detected -- score normalized to 1
Fuzzing⚠️ 0project is not fuzzed
License🟢 10license file detected
Signed-Releases⚠️ -1no releases found
Security-Policy🟢 9security policy file detected
Branch-Protection⚠️ 0branch protection not enabled on development/release branches
SAST🟢 10SAST tool is run on all commits
actions/astral-sh/setup-uv 6b9c6063abd6010835644d4c2e1bef4cf5cd0fca UnknownUnknown

Scanned Files

  • .github/workflows/pages-preview.yml

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 11, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a PR Preview GitHub Actions workflow to build MkDocs and Astro sites, merge outputs, inject a dynamic PR banner, upload and deploy previews to Cloudflare Pages, and clean up on PR close. Also adds CI/security workflows and documentation updates (site nav, new user guide, link fixes, minor docstring formatting).

Changes

Cohort / File(s) Summary
PR Preview Workflow
.github/workflows/pages-preview.yml
New PR Preview workflow: Build Preview (checkout, setup Python/Node, build MkDocs & Astro, guard merge of outputs, inject PR banner, upload preview-site artifact), Deploy Preview (download artifact, wrangler deploy to Cloudflare Pages, post/update PR comment), and Cleanup on PR close.
Workflow Security & CI
.github/workflows/zizmor.yml, .github/workflows/...
Adds workflow security analysis and other CI metadata workflows referenced in docs (zizmor and related CI entries).
Site content & nav
docs/user_guide.md, mkdocs.yml, docs/index.md
Adds comprehensive User Guide, updates mkdocs navigation (introduces User Guide and Developer Setup, removes previous Getting Started entry), and refactors index content and ordering.
Docs link fixes
docs/getting_started.md
Replaced relative "Next Steps" links with absolute GitHub URLs for CONTRIBUTING, CLAUDE, and DESIGN_SPEC.
CI Documentation
CLAUDE.md
Documents PR Preview workflow behavior, required Cloudflare secrets, concurrency/cleanup behavior, and other CI/security workflow additions.
Design Spec
DESIGN_SPEC.md
Records addition of new workflow files under .github/workflows.
Minor code formatting
src/ai_company/communication/messenger.py
Adjusted docstring indentation in receive() only; no behavioral changes.

Sequence Diagram(s)

sequenceDiagram
  participant PR as "Pull Request"
  participant GH as "GitHub Actions"
  participant Runner as "Actions Runner"
  participant MkDocs as "MkDocs (Python)"
  participant Astro as "Astro (Node)"
  participant Artifact as "Artifact Store"
  participant Wrangler as "wrangler"
  participant CF as "Cloudflare Pages"
  participant PRComment as "GitHub PR Comment"

  PR->>GH: open/update PR -> trigger pages-preview.yml
  GH->>Runner: run Build Preview
  Runner->>MkDocs: setup Python, build mkdocs site
  Runner->>Astro: setup Node, build astro site
  MkDocs-->>Runner: produced MkDocs output
  Astro-->>Runner: produced Astro output
  Runner->>Runner: merge outputs (guard against overwrite), inject PR-number banner into HTML
  Runner->>Artifact: upload `preview-site` artifact
  GH->>Runner: run Deploy Preview (download artifact)
  Runner->>Wrangler: deploy artifact to Cloudflare Pages
  Wrangler->>CF: create deployment -> returns preview URL
  Runner->>PRComment: post or update PR comment with preview URL and short SHA
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~35 minutes

Possibly related issues

Possibly related PRs

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely summarizes the main change: adding PR preview deployments via Cloudflare Pages, which aligns with the primary objective of the changeset.
Description check ✅ Passed The description is directly related to the changeset, providing clear details about the new workflow, security hardening, bug fixes, and documentation updates included in the PR.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/pr-preview-deployments
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch feat/pr-preview-deployments

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

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a new workflow for generating and deploying preview environments for pull requests using Cloudflare Pages. It also incorporates several security enhancements, bug fixes, and documentation updates to improve the overall development process.

Highlights

  • New Workflow: Introduces a new workflow (.github/workflows/pages-preview.yml) to build MkDocs + Astro on PRs, inject a 'Development Preview' banner, and deploy to Cloudflare Pages.
  • Preview URLs: Each PR now gets a unique preview URL at pr-<number>.synthorg-pr-preview.pages.dev, which is automatically commented on the PR and updated on re-push.
  • Security Hardening: Implements security measures including env vars for expression injection mitigation, fork PR guard, SHA-pinned actions, persist-credentials: false, and least-privilege permissions.
  • Bug Fixes: Addresses broken links in docs/getting_started.md and docstring indentation in messenger.py.
  • Documentation: Updates the CLAUDE.md CI section with PR Preview details.
Changelog
  • .github/workflows/pages-preview.yml
    • Added new workflow to build MkDocs + Astro on PRs, inject a 'Development Preview' banner, and deploy to Cloudflare Pages.
  • CLAUDE.md
    • Updated CI section with details about the new PR Preview workflow.
  • docs/getting_started.md
    • Replaced relative links with absolute links to improve navigation.
  • src/ai_company/communication/messenger.py
    • Corrected docstring indentation to resolve a griffe warning.
Ignored Files
  • Ignored by pattern: .github/workflows/** (1)
    • .github/workflows/pages-preview.yml
Activity
  • Addressed feedback from PR feat: add PR preview deployments via Cloudflare Pages #300 reviewers, including concurrency group with cancel-in-progress, paginated listComments, PR head SHA in comment, removal of unused deployments: write permission, and using Wrangler run step.
  • Implemented a test plan to verify workflow triggers, build success, banner appearance, deployment success, comment appearance, concurrency cancellation, and fork PR handling.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@github-actions
Copy link
Copy Markdown
Contributor

Preview Deployment

URL: https://pr-302.synthorg-pr-preview.pages.dev

Built from commit 8fd59de

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a new GitHub workflow for deploying PR previews to Cloudflare Pages, along with related documentation updates and minor fixes. The implementation is well-structured and includes security hardening measures. My review includes two suggestions for improving the documentation's readability and maintainability. One suggestion is to format a long descriptive line in CLAUDE.md for better readability. The other is to adjust the linking strategy in getting_started.md to ensure documentation links are version-correct across different branches and tags by making the linked files part of the documentation build.

CLAUDE.md Outdated

- **Jobs**: lint (ruff) + type-check (mypy src/ tests/) + test (pytest + coverage) run in parallel → ci-pass (gate)
- **Pages**: `.github/workflows/pages.yml` — builds Astro landing + MkDocs docs, merges, deploys to GitHub Pages on push to main
- **PR Preview**: `.github/workflows/pages-preview.yml` — builds site on PRs (same path triggers as Pages), injects "Development Preview" banner, deploys to Cloudflare Pages (`synthorg-pr-preview` project) via wrangler CLI. Each PR gets a unique preview URL at `pr-<number>.synthorg-pr-preview.pages.dev`. Requires `CLOUDFLARE_API_TOKEN` + `CLOUDFLARE_ACCOUNT_ID` secrets. Build job runs regardless (catches build failures); deploy job skips on fork PRs (no secrets access). Concurrency group cancels stale builds on rapid pushes.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

This line is very long (over 450 characters), which makes it difficult to read and maintain. For better readability, consider breaking this down into a nested list that details the different aspects of the PR Preview workflow.

Suggested change
- **PR Preview**: `.github/workflows/pages-preview.yml` — builds site on PRs (same path triggers as Pages), injects "Development Preview" banner, deploys to Cloudflare Pages (`synthorg-pr-preview` project) via wrangler CLI. Each PR gets a unique preview URL at `pr-<number>.synthorg-pr-preview.pages.dev`. Requires `CLOUDFLARE_API_TOKEN` + `CLOUDFLARE_ACCOUNT_ID` secrets. Build job runs regardless (catches build failures); deploy job skips on fork PRs (no secrets access). Concurrency group cancels stale builds on rapid pushes.
- **PR Preview**: `.github/workflows/pages-preview.yml`
- Builds site on PRs (same path triggers as Pages), injects a "Development Preview" banner, and deploys to Cloudflare Pages (`synthorg-pr-preview` project) via wrangler CLI.
- Each PR gets a unique preview URL at `pr-<number>.synthorg-pr-preview.pages.dev`.
- Requires `CLOUDFLARE_API_TOKEN` + `CLOUDFLARE_ACCOUNT_ID` secrets.
- The build job runs on all PRs to catch build failures, while the deploy job skips on fork PRs (no secrets access).
- A concurrency group is used to cancel stale builds on rapid pushes.

Comment on lines +153 to +155
- [CONTRIBUTING.md](https://github.com/Aureliolo/synthorg/blob/main/.github/CONTRIBUTING.md) — branch, commit, and PR workflow
- [CLAUDE.md](https://github.com/Aureliolo/synthorg/blob/main/CLAUDE.md) — code conventions and quick command reference
- [DESIGN_SPEC.md](https://github.com/Aureliolo/synthorg/blob/main/DESIGN_SPEC.md) — full high-level design specification
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Using absolute URLs pointing to the main branch can be problematic for documentation, as viewers on other branches or tags will be directed to content that may not match their version.

A more robust approach is to make these documentation assets part of the mkdocs build. You can copy these files into the docs directory during your CI build process and then use standard relative links. This ensures the documentation is self-contained and version-correct.

For example, you could add a step in your .github/workflows/pages-preview.yml and .github/workflows/pages.yml workflows:

- name: Copy root markdown files to docs
  run: |
    cp .github/CONTRIBUTING.md docs/
    cp CLAUDE.md docs/
    cp DESIGN_SPEC.md docs/

With that change, you can update the links in this file to be relative, as suggested.

Suggested change
- [CONTRIBUTING.md](https://github.com/Aureliolo/synthorg/blob/main/.github/CONTRIBUTING.md) — branch, commit, and PR workflow
- [CLAUDE.md](https://github.com/Aureliolo/synthorg/blob/main/CLAUDE.md) — code conventions and quick command reference
- [DESIGN_SPEC.md](https://github.com/Aureliolo/synthorg/blob/main/DESIGN_SPEC.md) — full high-level design specification
- [CONTRIBUTING.md](CONTRIBUTING.md) — branch, commit, and PR workflow
- [CLAUDE.md](CLAUDE.md) — code conventions and quick command reference
- [DESIGN_SPEC.md](DESIGN_SPEC.md) — full high-level design specification

@greptile-apps
Copy link
Copy Markdown

greptile-apps bot commented Mar 11, 2026

Greptile Summary

This PR adds a complete PR preview deployment pipeline via Cloudflare Pages, including a build job (MkDocs + Astro + preview banner injection), a deploy-preview job (Wrangler CLI deployment + PR comment), and a cleanup-preview job (comment deletion + Cloudflare deployment deletion on PR close). It also fixes 3 broken MkDocs --strict links, corrects a griffe docstring warning in messenger.py, and adds the design spec as a navigable MkDocs page.

The workflow demonstrates strong security hygiene: SHA-pinned actions, persist-credentials: false, permissions: {} at the top level, least-privilege per-job permissions, env var injection to prevent expression injection, and a fork PR guard on the deploy and cleanup jobs. Most feedback from PR #300 has been incorporated.

Key outstanding concerns:

  • Functional breakage (previously flagged, not yet fixed): upload-artifact is pinned to v7 while download-artifact is pinned to v8. These two major versions use incompatible internal formats — the download step will fail with "Artifact not found" on every run until both are aligned to the same major version.
  • Cleanup API errors silently masked: The || true on the Cloudflare API curl call in cleanup-preview means authentication failures, wrong account ID, or network errors are indistinguishable from "no deployments found" — the cleanup appears to succeed even when nothing was actually cleaned up.
  • Design spec duplication: docs/design_spec.md is a full copy of root-level DESIGN_SPEC.md. Since CLAUDE.md designates the root file as the authoritative source of truth, the two copies will drift over time without a mechanism to keep them in sync.

Confidence Score: 2/5

  • Not safe to merge as-is — the artifact major-version mismatch (v7 upload / v8 download) will cause every deploy-preview run to fail.
  • The workflow is thoughtfully constructed and most PR feat: add PR preview deployments via Cloudflare Pages #300 findings have been addressed. However, the upload-artifact@v7 / download-artifact@v8 mismatch (flagged in a previous review and still unresolved) is a hard functional breakage — the deploy-preview job will error on every PR push before any deployment attempt is made. Until that is fixed, the entire feature does not work end-to-end.
  • .github/workflows/pages-preview.yml (artifact version mismatch at lines 132/149, and API error masking in cleanup at line 265); docs/design_spec.md (duplicate of root DESIGN_SPEC.md).

Important Files Changed

Filename Overview
.github/workflows/pages-preview.yml New 280-line CI workflow for PR preview deployments; well-structured with security hardening, but has a functional artifact v7/v8 version mismatch (flagged previously, not yet fixed) that will cause deploy-preview to fail, plus a new issue where API errors in the cleanup step are silently swallowed by `
docs/design_spec.md New file: 3,527-line copy of the root-level DESIGN_SPEC.md added to make it navigable via MkDocs; creates a synchronization risk since CLAUDE.md designates the root file as the authoritative source of truth.
mkdocs.yml Added Design Specification: design_spec.md to nav; site_dir: _site/docs correctly configured so MkDocs output doesn't conflict with Astro's site/dist/ directory.
docs/getting_started.md Fixed 3 broken links that caused mkdocs build --strict to fail; links to CONTRIBUTING.md and CLAUDE.md use absolute GitHub URLs intentionally (files outside the docs/ hierarchy).
src/ai_company/communication/messenger.py Docstring indentation corrected to resolve a griffe warning during MkDocs API reference generation; no functional changes.
CLAUDE.md CI section updated with accurate PR Preview workflow details including secrets requirements, concurrency behavior, and fork PR handling.
site/src/pages/index.astro Updated landing page footer and content with /docs/design_spec/ link; link is valid given the new docs/design_spec.md page added in this PR.

Sequence Diagram

sequenceDiagram
    participant GH as GitHub
    participant Build as build job
    participant Deploy as deploy-preview job
    participant Cleanup as cleanup-preview job
    participant CF as Cloudflare Pages
    participant PR as PR Comment

    Note over GH,PR: On opened / synchronize / reopened
    GH->>Build: trigger (action != closed)
    Build->>Build: checkout @ head.sha
    Build->>Build: mkdocs build --strict → _site/docs/
    Build->>Build: npm ci + astro build → site/dist/
    Build->>Build: merge: cp site/dist/. _site/
    Build->>Build: inject preview banner (Python)
    Build->>GH: upload-artifact preview-site (v7)

    GH->>Deploy: trigger (needs: build, non-fork)
    Deploy->>GH: download-artifact preview-site (v8 ⚠️ mismatch)
    Deploy->>CF: wrangler pages deploy --branch=pr-N
    CF-->>Deploy: deployment live at pr-N.synthorg-pr-preview.pages.dev
    Deploy->>PR: create/update preview comment (paginated search)

    Note over GH,PR: On closed
    GH->>Cleanup: trigger (action == closed, non-fork)
    Cleanup->>PR: delete preview comment (paginated search)
    Cleanup->>CF: DELETE deployments for branch pr-N (per_page=100)
    Note over Cleanup,CF: API errors silently swallowed by || true ⚠️
Loading

Last reviewed commit: c05a276

Comment on lines +104 to +105
- name: Upload preview artifact
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Artifact upload/download major-version mismatch

actions/upload-artifact is pinned to # v7 but actions/download-artifact (line 121) is pinned to # v8. GitHub's artifact service uses a different internal format per major version — uploading with v7 and downloading with v8 (or vice versa) will result in the download step failing with "Artifact not found" or a format incompatibility error.

Both actions should reference the same major version. Pin both to the same version (typically the latest: both at v4, or both at whichever major version you intend):

Suggested change
- name: Upload preview artifact
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
- name: Upload preview artifact
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7

Fix: Update the download-artifact pin on line 121 to the same v7 SHA/tag used here, or update both to a consistent version.

Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/pages-preview.yml
Line: 104-105

Comment:
**Artifact upload/download major-version mismatch**

`actions/upload-artifact` is pinned to `# v7` but `actions/download-artifact` (line 121) is pinned to `# v8`. GitHub's artifact service uses a different internal format per major version — uploading with v7 and downloading with v8 (or vice versa) will result in the download step failing with "Artifact not found" or a format incompatibility error.

Both actions should reference the same major version. Pin both to the same version (typically the latest: both at v4, or both at whichever major version you intend):

```suggestion
      - name: Upload preview artifact
        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
```

> **Fix:** Update the `download-artifact` pin on line 121 to the same v7 SHA/tag used here, or update both to a consistent version.

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a PR-preview deployment pipeline for the docs + site build, enabling per-PR Cloudflare Pages previews, while also fixing a few documentation/build strictness issues.

Changes:

  • Added .github/workflows/pages-preview.yml to build MkDocs + Astro on PRs, inject a “Development Preview” banner, deploy to Cloudflare Pages, and comment the preview URL on the PR.
  • Fixed broken “Next Steps” links in docs/getting_started.md to satisfy strict MkDocs builds.
  • Updated CI documentation in CLAUDE.md and adjusted a docstring indentation in messenger.py.

Reviewed changes

Copilot reviewed 3 out of 4 changed files in this pull request and generated 4 comments.

File Description
.github/workflows/pages-preview.yml New PR workflow to build, deploy, and comment Cloudflare Pages preview URLs.
docs/getting_started.md Replaces relative links with absolute GitHub links to avoid MkDocs strict link failures.
CLAUDE.md Documents the new PR Preview workflow and its operational details.
src/ai_company/communication/messenger.py Docstring indentation adjustment related to MkDocstrings/Griffe parsing/rendering.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

- The bus is shut down while waiting.
- The subscription is cancelled via :meth:`unsubscribe`
while a ``receive()`` call is in flight.
while a ``receive()`` call is in flight.
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

Docstring list continuation is over-indented compared to the corresponding docstring in communication/bus_memory.py (which uses a 2-space continuation under the bullet). With the extra indentation here, some doc renderers treat the continuation as a nested block/code block. Align the while a ``receive()``... line indentation with the bullet text continuation (as in bus_memory.py).

Suggested change
while a ``receive()`` call is in flight.
while a ``receive()`` call is in flight.

Copilot uses AI. Check for mistakes.
if: github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
permissions:
contents: read
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

Comment preview URL uses github.rest.issues.* endpoints to list/update/create an issue comment, but this job only requests pull-requests: write. Grant issues: write (or replace pull-requests: write with issues: write) so the GITHUB_TOKEN can manage PR comments reliably.

Suggested change
contents: read
contents: read
issues: write

Copilot uses AI. Check for mistakes.
npm i --no-save wrangler@3 > /dev/null 2>&1
DEPLOY_OUTPUT=$(npx wrangler pages deploy _site --project-name=synthorg-pr-preview --branch="pr-${PR_NUMBER}" 2>&1)
echo "$DEPLOY_OUTPUT"
DEPLOY_URL=$(echo "$DEPLOY_OUTPUT" | grep -oP 'https://[^\s]+\.pages\.dev' | tail -1)
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

URL extraction uses grep ... | tail -1 under the default bash -eo pipefail runner shell; if grep finds no match, it exits 1 and the whole deploy step fails before the later “skip comment if no URL” logic can run. Make the extraction tolerant (e.g., || true) or avoid parsing by constructing the deterministic Pages URL from PR_NUMBER/project name or using a structured/JSON output flag if available.

Suggested change
DEPLOY_URL=$(echo "$DEPLOY_OUTPUT" | grep -oP 'https://[^\s]+\.pages\.dev' | tail -1)
DEPLOY_URL="https://pr-${PR_NUMBER}.synthorg-pr-preview.pages.dev"

Copilot uses AI. Check for mistakes.
Comment on lines +120 to +135
- name: Download preview artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: preview-site
path: _site

- name: Deploy to Cloudflare Pages
id: deploy
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
npm i --no-save wrangler@3 > /dev/null 2>&1
DEPLOY_OUTPUT=$(npx wrangler pages deploy _site --project-name=synthorg-pr-preview --branch="pr-${PR_NUMBER}" 2>&1)
echo "$DEPLOY_OUTPUT"
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

Deploy to Cloudflare Pages uses npm/npx but the deploy-preview job doesn’t set up/pin Node like the build job does. Add an explicit actions/setup-node step (matching the Node version used in build) to keep deployments reproducible and avoid failures if the runner image changes.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/pages-preview.yml:
- Around line 151-157: The current comment body uses the generic heading "##
Preview Deployment" which can clash with other comments; modify the constructed
comment (the const body that contains '## Preview Deployment') to append a
unique, non-visible marker string (e.g., an HTML comment like <!--
preview-deployment:unique-id -->) and then change the logic that
searches/upserts comments to only match existing comments that both include that
marker and are authored by the Actions bot (check comment.body.includes(marker)
&& comment.user?.login === 'github-actions[bot]' or equivalent); this ensures
updates target only the workflow-created comment.
- Around line 136-137: Check if DEPLOY_URL (the variable extracted with grep) is
empty before echoing to GITHUB_OUTPUT and fail the job if it is; update the
workflow step that sets DEPLOY_URL (the lines that assign DEPLOY_URL and echo
"deployment-url=${DEPLOY_URL}" >> "$GITHUB_OUTPUT") to validate the value and
call exit 1 (or use GitHub Actions ::error and exit) when DEPLOY_URL is blank so
the job fails instead of proceeding with an empty deployment URL.
🪄 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

Run ID: e7198302-bba1-43a1-b02e-c04861af2575

📥 Commits

Reviewing files that changed from the base of the PR and between 2eac571 and 8fd59de.

📒 Files selected for processing (4)
  • .github/workflows/pages-preview.yml
  • CLAUDE.md
  • docs/getting_started.md
  • src/ai_company/communication/messenger.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Agent
  • GitHub Check: Greptile Review
🧰 Additional context used
📓 Path-based instructions (3)
.github/workflows/*.yml

📄 CodeRabbit inference engine (CLAUDE.md)

.github/workflows/*.yml: Matrix: Python 3.14
Secret scanning: gitleaks workflow on push/PR + weekly schedule

Files:

  • .github/workflows/pages-preview.yml
**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.py: No from __future__ import annotations — Python 3.14 has PEP 649
Use except A, B: syntax without parentheses (PEP 758) — ruff enforces this on Python 3.14
Type hints required on all public functions; mypy strict mode
Google-style docstrings required on public classes and functions (enforced by ruff D rules)
Line length: 88 characters (ruff)
Functions must be less than 50 lines; files must be less than 800 lines

Files:

  • src/ai_company/communication/messenger.py
src/**/*.py

📄 CodeRabbit inference engine (CLAUDE.md)

src/**/*.py: Create new objects, never mutate existing ones. For non-Pydantic internal collections (registries, BaseTool), use copy.deepcopy() at construction + MappingProxyType wrapping for read-only enforcement. For dict/list fields in frozen Pydantic models, rely on frozen=True for field reassignment prevention and copy.deepcopy() at system boundaries (tool execution, LLM provider serialization, inter-agent delegation, serializing for persistence).
Config vs runtime state: use frozen Pydantic models for config/identity; use separate mutable-via-copy models (using model_copy(update=...)) for runtime state that evolves. Never mix static config fields with mutable runtime fields in one model.
Use Pydantic v2 (BaseModel, model_validator, computed_field, ConfigDict); use @computed_field for derived values instead of storing + validating redundant fields; use NotBlankStr from core.types for all identifier/name fields — including optional and tuple variants — instead of manual whitespace validators.
Prefer asyncio.TaskGroup for fan-out/fan-in parallel operations in new code (e.g. multiple tool invocations, parallel agent calls). Prefer structured concurrency over bare create_task.
Handle errors explicitly, never silently swallow
Validate at system boundaries (user input, external APIs, config files)
Every module with business logic MUST have: from ai_company.observability import get_logger then logger = get_logger(__name__). Never use import logging / logging.getLogger() / print() in application code.
Variable name for logger must always be logger (not _logger, not log)
Always use event name constants from domain-specific modules under ai_company.observability.events (e.g. PROVIDER_CALL_START from events.provider, BUDGET_RECORD_ADDED from events.budget, etc.). Import directly: from ai_company.observability.events.<domain> import EVENT_CONSTANT
Always use structured logging kwargs: `logger.info(EVENT, key=va...

Files:

  • src/ai_company/communication/messenger.py
🧠 Learnings (11)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/workflows/pages.yml : CI: `.github/workflows/pages.yml` — builds Astro landing + MkDocs docs, merges, deploys to GitHub Pages on push to main
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: After the PR exists, use `/aurelio-review-pr` to handle external reviewer feedback
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/workflows/zizmor.yml : Workflow security: `.github/workflows/zizmor.yml` — zizmor static analysis of GitHub Actions workflows on push to main and PRs (triggers only when workflow files change), SARIF upload to Security tab on push events only (fork PRs lack `security-events: write`)
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/workflows/release.yml : Release: `.github/workflows/release.yml` — Release Please (Google) auto-creates a release PR on every push to main. Merging the release PR creates a git tag (`vX.Y.Z`) + GitHub Release with changelog. Tag push triggers the Docker workflow to build version-tagged images. Uses `RELEASE_PLEASE_TOKEN` secret (PAT/GitHub App token) so tag creation triggers downstream workflows (GITHUB_TOKEN cannot). Config in `.github/release-please-config.json` and `.github/.release-please-manifest.json`.
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/workflows/docker.yml : CI: `.github/workflows/docker.yml` — build → scan → push to GHCR + cosign sign. Scans: Trivy (CRITICAL = hard fail, HIGH = warn-only) + Grype. CVE triage via `.github/.trivyignore.yaml` and `.github/.grype.yaml`. Images only pushed after scans pass. Triggers on push to main and version tags (`v*`).
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/dependabot.yml : Dependabot: daily uv + github-actions + docker updates, grouped minor/patch, no auto-merge
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Always read `DESIGN_SPEC.md` before implementing any feature or planning any issue

Applied to files:

  • docs/getting_started.md
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/workflows/pages.yml : CI: `.github/workflows/pages.yml` — builds Astro landing + MkDocs docs, merges, deploys to GitHub Pages on push to main

Applied to files:

  • .github/workflows/pages-preview.yml
  • CLAUDE.md
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/workflows/zizmor.yml : Workflow security: `.github/workflows/zizmor.yml` — zizmor static analysis of GitHub Actions workflows on push to main and PRs (triggers only when workflow files change), SARIF upload to Security tab on push events only (fork PRs lack `security-events: write`)

Applied to files:

  • .github/workflows/pages-preview.yml
  • CLAUDE.md
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/workflows/release.yml : Release: `.github/workflows/release.yml` — Release Please (Google) auto-creates a release PR on every push to main. Merging the release PR creates a git tag (`vX.Y.Z`) + GitHub Release with changelog. Tag push triggers the Docker workflow to build version-tagged images. Uses `RELEASE_PLEASE_TOKEN` secret (PAT/GitHub App token) so tag creation triggers downstream workflows (GITHUB_TOKEN cannot). Config in `.github/release-please-config.json` and `.github/.release-please-manifest.json`.

Applied to files:

  • .github/workflows/pages-preview.yml
  • CLAUDE.md
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/workflows/docker.yml : CI: `.github/workflows/docker.yml` — build → scan → push to GHCR + cosign sign. Scans: Trivy (CRITICAL = hard fail, HIGH = warn-only) + Grype. CVE triage via `.github/.trivyignore.yaml` and `.github/.grype.yaml`. Images only pushed after scans pass. Triggers on push to main and version tags (`v*`).

Applied to files:

  • .github/workflows/pages-preview.yml
  • CLAUDE.md
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/workflows/*.yml : Matrix: Python 3.14

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/dependabot.yml : Dependabot: daily uv + github-actions + docker updates, grouped minor/patch, no auto-merge

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: CI jobs: lint (ruff) + type-check (mypy src/ tests/) + test (pytest + coverage) run in parallel → ci-pass gate

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/workflows/*.yml : Secret scanning: gitleaks workflow on push/PR + weekly schedule

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to docker/** : Docker: backend uses 3-stage build (builder → setup → distroless runtime), Chainguard Python, non-root (UID 65532), CIS-hardened. Web: `nginxinc/nginx-unprivileged`, SPA routing, API/WebSocket proxy. Config: all Docker files in `docker/` — Dockerfiles, compose, `.env.example`.

Applied to files:

  • CLAUDE.md
🔇 Additional comments (4)
src/ai_company/communication/messenger.py (1)

294-294: Docstring continuation indentation fix looks correct.

This keeps the wrapped bullet item readable and tooling-friendly without changing behavior.

docs/getting_started.md (1)

153-155: Good fix for strict docs builds.

Using absolute GitHub URLs here avoids broken relative links to files outside the docs tree.

.github/workflows/pages-preview.yml (1)

13-18: Nice security posture for PR workflows.

Least-privilege permissions plus the fork-PR deploy guard are strong defaults for secret safety.

Also applies to: 114-119

CLAUDE.md (1)

176-176: CI documentation update is clear and useful.

The new PR Preview description captures trigger behavior, secret requirements, and fork-skip semantics well.

…ewers

- Revert messenger.py docstring over-indentation (comment-analyzer, Copilot)
- Add pages-preview.yml to DESIGN_SPEC.md file tree (docs-consistency)
- Harden deploy URL extraction with || true and explicit validation (Greptile, CodeRabbit, Copilot)
- Add HTML comment marker + bot-author check for comment upsert (CodeRabbit)
- Pin wrangler to 3.114.17 for reproducibility (Greptile)
- Only suppress stdout (not stderr) for npm install (Greptile)
- Add Node.js setup step to deploy-preview job (Copilot)
- Break long CLAUDE.md CI line into nested list (Gemini)
Griffe expects 4*2=8 spaces relative to docstring body (16 absolute)
for bullet continuation lines. The comment-analyzer and Copilot advice
to use 14 spaces was incorrect — griffe enforced in MkDocs strict mode.
Copilot AI review requested due to automatic review settings March 11, 2026 18:18
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 5 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

actions/checkout on pull_request defaults to checking out the PR merge commit (github.sha). In this workflow the PR comment reports pull_request.head.sha, which can diverge from what was actually built/deployed. To keep the deployed artifact and reported SHA consistent, either (a) set ref on the checkout step to github.event.pull_request.head.sha, or (b) keep building the merge commit but update the comment to report context.sha (and/or both SHAs).

Suggested change
persist-credentials: false
persist-credentials: false
ref: ${{ github.event.pull_request.head.sha }}

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

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 the current code and only fix it if needed.

Inline comments:
In @.github/workflows/pages-preview.yml:
- Around line 141-146: Replace the fragile grep-based extraction of DEPLOY_URL
with constructing the stable branch alias directly: remove the DEPLOY_OUTPUT
grep block that sets DEPLOY_URL and instead set
DEPLOY_URL="https://pr-${PR_NUMBER}.synthorg-pr-preview.pages.dev" (ensuring
PR_NUMBER is available from the workflow env or inputs), then write that value
to "$GITHUB_OUTPUT" as before; update any references to DEPLOY_URL in this job
to use the new constructed URL.
🪄 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

Run ID: 8c180e27-3e7a-4d7d-8cea-9ca47e0437db

📥 Commits

Reviewing files that changed from the base of the PR and between 8fd59de and c42cbea.

📒 Files selected for processing (3)
  • .github/workflows/pages-preview.yml
  • CLAUDE.md
  • DESIGN_SPEC.md
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Greptile Review
  • GitHub Check: Agent
🧰 Additional context used
📓 Path-based instructions (1)
.github/workflows/*.yml

📄 CodeRabbit inference engine (CLAUDE.md)

.github/workflows/*.yml: Matrix: Python 3.14
Secret scanning: gitleaks workflow on push/PR + weekly schedule

Files:

  • .github/workflows/pages-preview.yml
🧠 Learnings (12)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/workflows/pages.yml : CI: `.github/workflows/pages.yml` — builds Astro landing + MkDocs docs, merges, deploys to GitHub Pages on push to main
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: After the PR exists, use `/aurelio-review-pr` to handle external reviewer feedback
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/workflows/zizmor.yml : Workflow security: `.github/workflows/zizmor.yml` — zizmor static analysis of GitHub Actions workflows on push to main and PRs (triggers only when workflow files change), SARIF upload to Security tab on push events only (fork PRs lack `security-events: write`)
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Local docs preview: run `uv run mkdocs serve` (http://127.0.0.1:8000)
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/workflows/zizmor.yml : Workflow security: `.github/workflows/zizmor.yml` — zizmor static analysis of GitHub Actions workflows on push to main and PRs (triggers only when workflow files change), SARIF upload to Security tab on push events only (fork PRs lack `security-events: write`)

Applied to files:

  • DESIGN_SPEC.md
  • CLAUDE.md
  • .github/workflows/pages-preview.yml
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/workflows/pages.yml : CI: `.github/workflows/pages.yml` — builds Astro landing + MkDocs docs, merges, deploys to GitHub Pages on push to main

Applied to files:

  • DESIGN_SPEC.md
  • CLAUDE.md
  • .github/workflows/pages-preview.yml
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/dependabot.yml : Dependabot: daily uv + github-actions + docker updates, grouped minor/patch, no auto-merge

Applied to files:

  • DESIGN_SPEC.md
  • CLAUDE.md
  • .github/workflows/pages-preview.yml
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/workflows/docker.yml : CI: `.github/workflows/docker.yml` — build → scan → push to GHCR + cosign sign. Scans: Trivy (CRITICAL = hard fail, HIGH = warn-only) + Grype. CVE triage via `.github/.trivyignore.yaml` and `.github/.grype.yaml`. Images only pushed after scans pass. Triggers on push to main and version tags (`v*`).

Applied to files:

  • DESIGN_SPEC.md
  • CLAUDE.md
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/workflows/*.yml : Secret scanning: gitleaks workflow on push/PR + weekly schedule

Applied to files:

  • DESIGN_SPEC.md
  • CLAUDE.md
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/workflows/release.yml : Release: `.github/workflows/release.yml` — Release Please (Google) auto-creates a release PR on every push to main. Merging the release PR creates a git tag (`vX.Y.Z`) + GitHub Release with changelog. Tag push triggers the Docker workflow to build version-tagged images. Uses `RELEASE_PLEASE_TOKEN` secret (PAT/GitHub App token) so tag creation triggers downstream workflows (GITHUB_TOKEN cannot). Config in `.github/release-please-config.json` and `.github/.release-please-manifest.json`.

Applied to files:

  • DESIGN_SPEC.md
  • CLAUDE.md
  • .github/workflows/pages-preview.yml
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/workflows/*.yml : Matrix: Python 3.14

Applied to files:

  • DESIGN_SPEC.md
  • CLAUDE.md
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Never create a PR directly — `gh pr create` is blocked by hookify. Always use `/pre-pr-review` to create PRs — it runs automated checks + review agents + fixes before creating the PR. For trivial/docs-only changes: `/pre-pr-review quick` skips agents but still runs automated checks.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: CI jobs: lint (ruff) + type-check (mypy src/ tests/) + test (pytest + coverage) run in parallel → ci-pass gate

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to docker/** : Docker: backend uses 3-stage build (builder → setup → distroless runtime), Chainguard Python, non-root (UID 65532), CIS-hardened. Web: `nginxinc/nginx-unprivileged`, SPA routing, API/WebSocket proxy. Config: all Docker files in `docker/` — Dockerfiles, compose, `.env.example`.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Dependency review: license allow-list (permissive only), PR comment summaries

Applied to files:

  • .github/workflows/pages-preview.yml

…, cleanup

- Checkout PR head SHA instead of merge commit (Copilot #13)
- Use deterministic branch alias URL instead of grep-parsed deploy URL (CodeRabbit #14)
- Add cleanup job to delete Cloudflare deployments on PR close (Greptile #15)
- Skip build/deploy jobs on PR close events
- Update CLAUDE.md with new workflow capabilities
Split documentation into two tracks:
- User Guide: install, configure, run (for users of the framework)
- Developer Setup: clone, test, lint, IDE (for contributors)

Both linked side-by-side on the docs home page.
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

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 the current code and only fix it if needed.

Inline comments:
In @.github/workflows/pages-preview.yml:
- Around line 210-222: The cleanup currently only reads the first page and
swallows errors; update the deployment listing to iterate pages (use a page loop
with a page query param and per_page to collect all matching .result[] ids for
the BRANCH into DEPLOYMENTS), remove the blanket "|| true" on the curl/jq list
so failures are detectable, and when deleting each DEPLOY_ID call the DELETE
endpoint without "|| true" and check the HTTP response body/status: if the
delete fails log/exit with the error details (including response body) so failed
deletions are surfaced; additionally detect and handle the Cloudflare "cannot
delete latest deployment" error case (log it with DEPLOY_ID and suggest skipping
or retrying after creating a new deployment) while continuing to paginate and
attempt other deletions using CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID.
🪄 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

Run ID: e1402680-a146-4c5c-be7d-ea4df6655d4c

📥 Commits

Reviewing files that changed from the base of the PR and between c42cbea and e809aeb.

📒 Files selected for processing (2)
  • .github/workflows/pages-preview.yml
  • CLAUDE.md
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Greptile Review
🧰 Additional context used
📓 Path-based instructions (1)
.github/workflows/*.yml

📄 CodeRabbit inference engine (CLAUDE.md)

.github/workflows/*.yml: Matrix: Python 3.14
Secret scanning: gitleaks workflow on push/PR + weekly schedule

Files:

  • .github/workflows/pages-preview.yml
🧠 Learnings (13)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: After the PR exists, use `/aurelio-review-pr` to handle external reviewer feedback
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/workflows/pages.yml : CI: `.github/workflows/pages.yml` — builds Astro landing + MkDocs docs, merges, deploys to GitHub Pages on push to main
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/workflows/zizmor.yml : Workflow security: `.github/workflows/zizmor.yml` — zizmor static analysis of GitHub Actions workflows on push to main and PRs (triggers only when workflow files change), SARIF upload to Security tab on push events only (fork PRs lack `security-events: write`)
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/workflows/release.yml : Release: `.github/workflows/release.yml` — Release Please (Google) auto-creates a release PR on every push to main. Merging the release PR creates a git tag (`vX.Y.Z`) + GitHub Release with changelog. Tag push triggers the Docker workflow to build version-tagged images. Uses `RELEASE_PLEASE_TOKEN` secret (PAT/GitHub App token) so tag creation triggers downstream workflows (GITHUB_TOKEN cannot). Config in `.github/release-please-config.json` and `.github/.release-please-manifest.json`.
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/workflows/docker.yml : CI: `.github/workflows/docker.yml` — build → scan → push to GHCR + cosign sign. Scans: Trivy (CRITICAL = hard fail, HIGH = warn-only) + Grype. CVE triage via `.github/.trivyignore.yaml` and `.github/.grype.yaml`. Images only pushed after scans pass. Triggers on push to main and version tags (`v*`).
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/workflows/*.yml : Secret scanning: gitleaks workflow on push/PR + weekly schedule
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/workflows/docker.yml : CI: `.github/workflows/docker.yml` — build → scan → push to GHCR + cosign sign. Scans: Trivy (CRITICAL = hard fail, HIGH = warn-only) + Grype. CVE triage via `.github/.trivyignore.yaml` and `.github/.grype.yaml`. Images only pushed after scans pass. Triggers on push to main and version tags (`v*`).

Applied to files:

  • CLAUDE.md
  • .github/workflows/pages-preview.yml
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/workflows/release.yml : Release: `.github/workflows/release.yml` — Release Please (Google) auto-creates a release PR on every push to main. Merging the release PR creates a git tag (`vX.Y.Z`) + GitHub Release with changelog. Tag push triggers the Docker workflow to build version-tagged images. Uses `RELEASE_PLEASE_TOKEN` secret (PAT/GitHub App token) so tag creation triggers downstream workflows (GITHUB_TOKEN cannot). Config in `.github/release-please-config.json` and `.github/.release-please-manifest.json`.

Applied to files:

  • CLAUDE.md
  • .github/workflows/pages-preview.yml
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/workflows/zizmor.yml : Workflow security: `.github/workflows/zizmor.yml` — zizmor static analysis of GitHub Actions workflows on push to main and PRs (triggers only when workflow files change), SARIF upload to Security tab on push events only (fork PRs lack `security-events: write`)

Applied to files:

  • CLAUDE.md
  • .github/workflows/pages-preview.yml
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/workflows/*.yml : Secret scanning: gitleaks workflow on push/PR + weekly schedule

Applied to files:

  • CLAUDE.md
  • .github/workflows/pages-preview.yml
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/workflows/pages.yml : CI: `.github/workflows/pages.yml` — builds Astro landing + MkDocs docs, merges, deploys to GitHub Pages on push to main

Applied to files:

  • CLAUDE.md
  • .github/workflows/pages-preview.yml
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/dependabot.yml : Dependabot: daily uv + github-actions + docker updates, grouped minor/patch, no auto-merge

Applied to files:

  • CLAUDE.md
  • .github/workflows/pages-preview.yml
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Never create a PR directly — `gh pr create` is blocked by hookify. Always use `/pre-pr-review` to create PRs — it runs automated checks + review agents + fixes before creating the PR. For trivial/docs-only changes: `/pre-pr-review quick` skips agents but still runs automated checks.

Applied to files:

  • CLAUDE.md
  • .github/workflows/pages-preview.yml
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to .github/workflows/*.yml : Matrix: Python 3.14

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: CI jobs: lint (ruff) + type-check (mypy src/ tests/) + test (pytest + coverage) run in parallel → ci-pass gate

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Applies to docker/** : Docker: backend uses 3-stage build (builder → setup → distroless runtime), Chainguard Python, non-root (UID 65532), CIS-hardened. Web: `nginxinc/nginx-unprivileged`, SPA routing, API/WebSocket proxy. Config: all Docker files in `docker/` — Dockerfiles, compose, `.env.example`.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: Dependency review: license allow-list (permissive only), PR comment summaries

Applied to files:

  • .github/workflows/pages-preview.yml
📚 Learning: 2026-03-11T15:09:59.075Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-11T15:09:59.075Z
Learning: When review agents find valid issues (including pre-existing issues in surrounding code, suggestions, and findings adjacent to the PR's changes), fix them all. No deferring, no 'out of scope' skipping.

Applied to files:

  • .github/workflows/pages-preview.yml

Comment on lines +210 to +222
DEPLOYMENTS=$(curl -sf \
"https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/pages/projects/synthorg-pr-preview/deployments" \
-H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \
| jq -r ".result[] | select(.deployment_trigger.metadata.branch == \"${BRANCH}\") | .id") || true
if [ -z "$DEPLOYMENTS" ]; then
echo "No deployments found for branch ${BRANCH}"
exit 0
fi
for DEPLOY_ID in $DEPLOYMENTS; do
echo "Deleting deployment ${DEPLOY_ID}..."
curl -sf -X DELETE \
"https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/pages/projects/synthorg-pr-preview/deployments/${DEPLOY_ID}?force=true" \
-H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" || true
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 | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Cloudflare Pages deployments API pagination behavior documentation

💡 Result:

Cloudflare Pages “Get deployments” (GET /accounts/{account_id}/pages/projects/{project_name}/deployments) uses page-based pagination:

  • Request pagination params: page (which page to fetch) and per_page (items per page). It also supports env=production|preview to filter which deployments are listed. [1]
  • Response pagination metadata is in result_info, which includes at least count, page, per_page, and total_count (total deployments matching the query). [1]
  • This matches Cloudflare’s general API pagination model: use page=x and per_page=xx, and read the pagination fields from result_info. [2]

Sources: [1] Cloudflare API Reference – Pages “Get deployments”; [2] Cloudflare Fundamentals – “Make API calls” (Pagination section.


🌐 Web query:

Cloudflare Pages API delete deployment latest branch restriction

💡 Result:

Cloudflare Pages does have a Delete Deployment endpoint:

  • DELETE /client/v4/accounts/{account_id}/pages/projects/{project_name}/deployments/{deployment_id} (requires Pages Write) [1]

But there’s an important restriction:

  • You cannot delete the latest (most recent) deployment for any given branch. Cloudflare’s own Pages API example for “deleting old deployments” explicitly notes: “The latest deployment for a branch cannot be deleted.” [2]

Implication / workaround:

  • If you need to delete what is currently the latest deployment on a branch, you must first make another newer deployment on that same branch (so the one you want to remove is no longer the latest), and then delete it via the endpoint above. [2][1]

Sources:
[1] Cloudflare API – Pages Deployments: Delete Deployment
[2] Cloudflare Pages docs – REST API (example: deleting old deployments; latest-per-branch can’t be deleted)


🏁 Script executed:

# First, let's examine the full workflow file to understand the context
cd .github/workflows && ls -la pages-preview.yml && wc -l pages-preview.yml

Repository: Aureliolo/synthorg

Length of output: 150


🏁 Script executed:

# Let's look at the cleanup job section more carefully, including context around lines 210-222
sed -n '200,230p' .github/workflows/pages-preview.yml

Repository: Aureliolo/synthorg

Length of output: 1376


🏁 Script executed:

# Check if there are any pagination parameters or loop handling in the entire cleanup job
sed -n '180,230p' .github/workflows/pages-preview.yml

Repository: Aureliolo/synthorg

Length of output: 1979


Cleanup cannot guarantee the preview is removed.

Cloudflare's Pages deployments endpoint uses page-based pagination, but the cleanup script never requests pagination parameters and only processes the first page. Additionally, Cloudflare's API explicitly prevents deletion of the latest deployment for any given branch. Since both the list and delete operations suppress errors with || true, failed deletions go unnoticed, leaving closed PRs with orphaned preview deployments while the job reports success.

To address this, surface failed deletions explicitly and implement pagination handling. Longer term, a different strategy is needed to handle the latest-deployment restriction—either by first triggering a new deployment on the branch (making the old one no longer latest) before deletion, or by finding an alternative cleanup approach.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/pages-preview.yml around lines 210 - 222, The cleanup
currently only reads the first page and swallows errors; update the deployment
listing to iterate pages (use a page loop with a page query param and per_page
to collect all matching .result[] ids for the BRANCH into DEPLOYMENTS), remove
the blanket "|| true" on the curl/jq list so failures are detectable, and when
deleting each DEPLOY_ID call the DELETE endpoint without "|| true" and check the
HTTP response body/status: if the delete fails log/exit with the error details
(including response body) so failed deletions are surfaced; additionally detect
and handle the Cloudflare "cannot delete latest deployment" error case (log it
with DEPLOY_ID and suggest skipping or retrying after creating a new deployment)
while continuing to paginate and attempt other deletions using
CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID.

… dead code

- Delete preview comment on PR close before deleting deployments (Greptile #16)
- Add pull-requests: write to cleanup job for comment deletion
- Add per_page=100 to Cloudflare deployments API call (Greptile #17, CodeRabbit #19)
- Surface failed deletions with warnings instead of silent || true
- Note Cloudflare restriction: latest deployment per branch cannot be deleted
- Remove unreachable if (!url) guard — PREVIEW_URL is always set (Greptile #18)
Copilot AI review requested due to automatic review settings March 11, 2026 18:48
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 8 out of 10 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

deploy-preview uses github.rest.issues.* (listComments/updateComment/createComment), but the job permissions only grant pull-requests: write. With top-level permissions: {}, this can lead to 403s because issue comments typically require issues: write. Add issues: write (and drop pull-requests: write if it’s no longer needed) so the preview URL comment can be created/updated reliably.

Suggested change
pull-requests: write
issues: write

Copilot uses AI. Check for mistakes.
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

cleanup-preview deletes the PR comment via github.rest.issues.deleteComment, but the job permissions only grant pull-requests: write. With top-level permissions: {}, this may fail due to missing issues: write. Grant issues: write here as well to ensure cleanup can remove the preview comment.

Suggested change
pull-requests: write
pull-requests: write
issues: write

Copilot uses AI. Check for mistakes.
Comment on lines +122 to +126
updated = re.sub(r"(<body[^>]*>)", lambda m: m.group(1) + banner, text, count=1)
if updated != text:
f.write_text(updated, encoding="utf-8")
count += 1
print(f"Injected preview banner into {count} HTML files")
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

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

The banner injection step logs how many HTML files were updated, but it never fails the build if count ends up as 0 (e.g., if the <body> regex doesn’t match). Since the banner is a key safety/UX signal for previews, consider failing the job when no files were modified (and/or make the <body> match case-insensitive) so a regression doesn’t silently ship an un-bannered preview.

Suggested change
updated = re.sub(r"(<body[^>]*>)", lambda m: m.group(1) + banner, text, count=1)
if updated != text:
f.write_text(updated, encoding="utf-8")
count += 1
print(f"Injected preview banner into {count} HTML files")
updated = re.sub(r"(?i)(<body[^>]*>)", lambda m: m.group(1) + banner, text, count=1)
if updated != text:
f.write_text(updated, encoding="utf-8")
count += 1
print(f"Injected preview banner into {count} HTML files")
if count == 0:
raise SystemExit("No HTML files were modified with the preview banner; failing build to avoid un-bannered preview.")

Copilot uses AI. Check for mistakes.
Comment on lines +50 to +54
- name: Set up Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: "22"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

npm dependency cache not configured in either Node.js setup step

Both setup-node calls in the workflow (here in build and again in deploy-preview at line 151) omit the cache input, so npm ci downloads Astro's entire dependency tree fresh on every PR push. For a preview workflow triggered on every synchronize event, this adds avoidable latency and bandwidth.

Adding cache: 'npm' (which keys on package-lock.json) is a one-liner:

Suggested change
- name: Set up Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: "22"
- name: Set up Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: "22"
cache: 'npm'
cache-dependency-path: site/package-lock.json

The same change applies to the deploy-preview job's setup-node step at line 151. astral-sh/setup-uv similarly supports an enable-cache option that would cache Python package downloads between runs.

Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/pages-preview.yml
Line: 50-54

Comment:
**npm dependency cache not configured in either Node.js setup step**

Both `setup-node` calls in the workflow (here in `build` and again in `deploy-preview` at line 151) omit the `cache` input, so `npm ci` downloads Astro's entire dependency tree fresh on every PR push. For a preview workflow triggered on every `synchronize` event, this adds avoidable latency and bandwidth.

Adding `cache: 'npm'` (which keys on `package-lock.json`) is a one-liner:

```suggestion
      - name: Set up Node.js
        uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
        with:
          node-version: "22"
          cache: 'npm'
          cache-dependency-path: site/package-lock.json
```

The same change applies to the `deploy-preview` job's `setup-node` step at line 151. `astral-sh/setup-uv` similarly supports an `enable-cache` option that would cache Python package downloads between runs.

How can I resolve this? If you propose a fix, please make it concise.

- Truncate build timestamp to milliseconds (Safari rejects microsecond
  precision in Date constructor, causing "NaN ago" display)
- Add timeout-minutes to all three jobs (10m build, 5m deploy, 5m cleanup)
@Aureliolo Aureliolo merged commit b73c45a into main Mar 11, 2026
11 of 12 checks passed
@Aureliolo Aureliolo deleted the feat/pr-preview-deployments branch March 11, 2026 19:35
@@ -0,0 +1,3527 @@
# SynthOrg - High-Level Design Specification
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Duplicated design spec creates synchronization risk

docs/design_spec.md is a full 3,527-line copy of the root-level DESIGN_SPEC.md. CLAUDE.md explicitly designates DESIGN_SPEC.md as the authoritative source of truth (with a MANDATORY instruction to always read it before implementing features). Having an independent copy under docs/ means these two files can silently drift apart — a contributor updating the root spec won't necessarily update the docs copy, and vice versa.

A safer approach is to use a MkDocs docs_dir-relative symlink or to reconfigure mkdocs.yml to resolve the file from the repo root:

# mkdocs.yml — resolve the design spec from the repo root
# (requires docs_dir or an explicit file mapping)
nav:
  - Design Specification: ../DESIGN_SPEC.md

Or, if MkDocs doesn't support ../ paths out of the box, a pre-build step that copies/symlinks the root file would keep a single source of truth instead of two diverging copies.

Prompt To Fix With AI
This is a comment left during a code review.
Path: docs/design_spec.md
Line: 1

Comment:
**Duplicated design spec creates synchronization risk**

`docs/design_spec.md` is a full 3,527-line copy of the root-level `DESIGN_SPEC.md`. `CLAUDE.md` explicitly designates `DESIGN_SPEC.md` as the authoritative source of truth (with a `MANDATORY` instruction to always read it before implementing features). Having an independent copy under `docs/` means these two files can silently drift apart — a contributor updating the root spec won't necessarily update the docs copy, and vice versa.

A safer approach is to use a MkDocs `docs_dir`-relative symlink or to reconfigure `mkdocs.yml` to resolve the file from the repo root:

```yaml
# mkdocs.yml — resolve the design spec from the repo root
# (requires docs_dir or an explicit file mapping)
nav:
  - Design Specification: ../DESIGN_SPEC.md
```

Or, if MkDocs doesn't support `../` paths out of the box, a pre-build step that copies/symlinks the root file would keep a single source of truth instead of two diverging copies.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +165 to +166
npm i --no-save wrangler@3.114.17 > /dev/null
npx wrangler pages deploy _site --project-name=synthorg-pr-preview --branch="pr-${PR_NUMBER}"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

npm install stdout suppression hides progress, consider redirecting to a log

npm i --no-save wrangler@3.114.17 > /dev/null discards stdout entirely, so if Wrangler takes an unexpectedly long time to install (large dependency tree, slow npm mirror, etc.) the step produces no visible output during that wait window. Stderr is still visible so genuine errors will surface, but a hung npm install would produce a silent, blank step log with no clue about what it is doing.

Consider redirecting to a step debug log file instead:

Suggested change
npm i --no-save wrangler@3.114.17 > /dev/null
npx wrangler pages deploy _site --project-name=synthorg-pr-preview --branch="pr-${PR_NUMBER}"
npm i --no-save wrangler@3.114.17 2>&1 | grep -v "^npm warn" || true
npx wrangler pages deploy _site --project-name=synthorg-pr-preview --branch="pr-${PR_NUMBER}"

Or at minimum, only suppress the verbose npm progress lines:

npm i --no-save wrangler@3.114.17 --loglevel=error

This keeps error output but hides the noisy download progress.

Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/pages-preview.yml
Line: 165-166

Comment:
**`npm install` stdout suppression hides progress, consider redirecting to a log**

`npm i --no-save wrangler@3.114.17 > /dev/null` discards stdout entirely, so if Wrangler takes an unexpectedly long time to install (large dependency tree, slow npm mirror, etc.) the step produces no visible output during that wait window. Stderr is still visible so genuine errors will surface, but a hung `npm install` would produce a silent, blank step log with no clue about what it is doing.

Consider redirecting to a step debug log file instead:

```suggestion
          npm i --no-save wrangler@3.114.17 2>&1 | grep -v "^npm warn" || true
          npx wrangler pages deploy _site --project-name=synthorg-pr-preview --branch="pr-${PR_NUMBER}"
```

Or at minimum, only suppress the verbose npm progress lines:
```bash
npm i --no-save wrangler@3.114.17 --loglevel=error
```
This keeps error output but hides the noisy download progress.

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +265 to +271
| jq -r ".result[] | select(.deployment_trigger.metadata.branch == \"${BRANCH}\") | .id") || true
if [ -z "$DEPLOYMENTS" ]; then
echo "No deployments found for branch ${BRANCH}"
exit 0
fi
for DEPLOY_ID in $DEPLOYMENTS; do
echo "Deleting deployment ${DEPLOY_ID}..."
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

|| true silently swallows Cloudflare API authentication and network errors

The || true on the DEPLOYMENTS=$(curl -sf ... | jq ...) pipeline causes any API-level failure to look identical to "no deployments found." Specifically:

  • curl -sf exits non-zero if the HTTP status is 4xx or 5xx (wrong token, wrong account ID, project not found, rate-limited) or if the connection fails entirely
  • When that happens, the pipeline returns non-zero, || true converts it to success, $DEPLOYMENTS is empty, and the next if [ -z "$DEPLOYMENTS" ] branch prints "No deployments found for branch pr-N" and exits 0

A Cloudflare API call with invalid credentials would appear as a successful cleanup with no deployments — the stale preview branch would silently remain, and there is no indication in the workflow logs that anything went wrong.

To distinguish "no deployments for this branch" from "API call failed":

RESPONSE=$(curl -sf \
  "https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/pages/projects/synthorg-pr-preview/deployments?per_page=100" \
  -H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}") || {
  echo "::error::Cloudflare API request failed — check CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID"
  exit 1
}
DEPLOYMENTS=$(echo "$RESPONSE" | jq -r ".result[] | select(.deployment_trigger.metadata.branch == \"${BRANCH}\") | .id")
Prompt To Fix With AI
This is a comment left during a code review.
Path: .github/workflows/pages-preview.yml
Line: 265-271

Comment:
**`|| true` silently swallows Cloudflare API authentication and network errors**

The `|| true` on the `DEPLOYMENTS=$(curl -sf ... | jq ...)` pipeline causes any API-level failure to look identical to "no deployments found." Specifically:

- `curl -sf` exits non-zero if the HTTP status is 4xx or 5xx (wrong token, wrong account ID, project not found, rate-limited) **or** if the connection fails entirely
- When that happens, the pipeline returns non-zero, `|| true` converts it to success, `$DEPLOYMENTS` is empty, and the next `if [ -z "$DEPLOYMENTS" ]` branch prints `"No deployments found for branch pr-N"` and exits 0

A Cloudflare API call with invalid credentials would appear as a successful cleanup with no deployments — the stale preview branch would silently remain, and there is no indication in the workflow logs that anything went wrong.

To distinguish "no deployments for this branch" from "API call failed":

```bash
RESPONSE=$(curl -sf \
  "https://api.cloudflare.com/client/v4/accounts/${CLOUDFLARE_ACCOUNT_ID}/pages/projects/synthorg-pr-preview/deployments?per_page=100" \
  -H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}") || {
  echo "::error::Cloudflare API request failed — check CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID"
  exit 1
}
DEPLOYMENTS=$(echo "$RESPONSE" | jq -r ".result[] | select(.deployment_trigger.metadata.branch == \"${BRANCH}\") | .id")
```

How can I resolve this? If you propose a fix, please make it concise.

Aureliolo added a commit that referenced this pull request Mar 11, 2026
The deploy-pages action was pinned to a non-existent SHA (typo from
PR #298), masked by upstream MkDocs build failures until PR #302 fixed
them. Corrected to the actual v4.0.5 SHA.

The preview cleanup silently swallowed Cloudflare API errors via
curl -sf + || true, causing "No deployments found" even when
deployments existed. Now surfaces HTTP errors, logs available branches
on miss, and uses jq --arg for safer filtering.
Aureliolo added a commit that referenced this pull request Mar 11, 2026
…#304)

## Summary

- **Fix deploy-pages SHA**: The `actions/deploy-pages` action in
`pages.yml` was pinned to SHA `...53fd0d31` which doesn't exist.
Corrected to `...b3c0c03e` (v4.0.5). This typo was introduced in PR #298
but was masked by MkDocs build failures — only surfaced after PR #302
fixed the build.
- **Fix preview cleanup**: The Cloudflare deployment cleanup in
`pages-preview.yml` silently swallowed API errors (`curl -sf` + `||
true`), causing PR #302's cleanup to report "No deployments found" while
the deployment remained live. Now properly surfaces HTTP errors, logs
available branches on miss for diagnostics, and uses `jq --arg` for
safer filtering.

## Test plan

- [ ] GitHub Pages workflow succeeds on merge (deploy step reaches
GitHub Pages)
- [ ] PR preview cleanup correctly lists and deletes Cloudflare
deployments on PR close
- [ ] Cleanup logs show diagnostic info if no matching deployments found
Aureliolo added a commit that referenced this pull request Mar 11, 2026
🤖 I have created a release *beep* *boop*
---


##
[0.1.1](v0.1.0...v0.1.1)
(2026-03-11)


### Features

* add PR preview deployments via Cloudflare Pages
([#302](#302))
([b73c45a](b73c45a))


### Bug Fixes

* correct deploy-pages SHA and improve preview cleanup reliability
([#304](#304))
([584d64a](584d64a))
* harden API key hashing with HMAC-SHA256 and clean up legacy changelog
([#292](#292))
([5e85353](5e85353))
* upgrade upload-pages-artifact to v4 and add zizmor workflow linting
([#299](#299))
([2eac571](2eac571))
* use Cloudflare Pages API default per_page for pagination
([#305](#305))
([9fec245](9fec245))


### Documentation

* remove milestone references and rebrand to SynthOrg
([#289](#289))
([57a03e0](57a03e0))
* set up documentation site, release CI, and sandbox hardening
([#298](#298))
([0dec9da](0dec9da))
* split DESIGN_SPEC.md into 7 focused design pages
([#308](#308))
([9ea0788](9ea0788))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

---------

Signed-off-by: Aurelio <19254254+Aureliolo@users.noreply.github.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.

2 participants