Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
841fb13
feat: add PR preview deployments via Cloudflare Pages
Aureliolo Mar 11, 2026
ddad993
fix: address review findings — security hardening and consistency
Aureliolo Mar 11, 2026
a7ddb81
fix: rename Cloudflare Pages project to match existing project
Aureliolo Mar 11, 2026
532bcbf
fix: address PR review feedback — concurrency, pagination, head SHA
Aureliolo Mar 11, 2026
8fd59de
fix: incorporate PR review feedback and rename Cloudflare project
Aureliolo Mar 11, 2026
0785644
fix: address 8 PR review findings from local agents and external revi…
Aureliolo Mar 11, 2026
c42cbea
fix: restore correct griffe-compatible docstring indentation
Aureliolo Mar 11, 2026
e809aeb
fix: address round 2 reviewer feedback — SHA consistency, stable URLs…
Aureliolo Mar 11, 2026
bafe318
docs: add barebones user guide alongside developer setup
Aureliolo Mar 11, 2026
7aee04b
fix: address round 3 reviewer feedback — cleanup comment, pagination,…
Aureliolo Mar 11, 2026
208a9a5
feat: add commit SHA and dynamic build age to preview banner
Aureliolo Mar 11, 2026
c28baab
docs: add template option to user guide, de-emphasize raw YAML
Aureliolo Mar 11, 2026
78c5219
docs: add grid cards for user/dev paths, lead user guide with Docker
Aureliolo Mar 11, 2026
35222d5
docs: simplify user guide — all config via dashboard, add WIP disclaimer
Aureliolo Mar 11, 2026
632f175
fix: remove dead placeholder links from user guide
Aureliolo Mar 11, 2026
8e41d35
fix: landing page links to user guide, Docker commands, templates in …
Aureliolo Mar 11, 2026
f3e43ba
docs: render design spec in docs site, fix landing page links and mes…
Aureliolo Mar 11, 2026
c05a276
fix: Safari ISO timestamp compatibility and add job timeouts
Aureliolo Mar 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
276 changes: 276 additions & 0 deletions .github/workflows/pages-preview.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,276 @@
name: PR Preview

on:
pull_request:
branches: [main]
types: [opened, synchronize, reopened, closed]
paths:
- "docs/**"
- "site/**"
- "mkdocs.yml"
- "src/ai_company/**"
- ".github/workflows/pages-preview.yml"
Comment thread
greptile-apps[bot] marked this conversation as resolved.

permissions: {}

concurrency:
group: "pages-preview-${{ github.event.pull_request.number }}"
cancel-in-progress: true

jobs:
build:
name: Build Preview
if: github.event.action != 'closed'
runs-on: ubuntu-latest
Comment thread
greptile-apps[bot] marked this conversation as resolved.
permissions:
contents: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
persist-credentials: false

Copilot AI Mar 11, 2026

Copy link

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.
ref: ${{ github.event.pull_request.head.sha }}

# --- MkDocs (documentation at /docs) ---
- name: Set up Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: "3.14"
allow-prereleases: true

- name: Install uv
uses: astral-sh/setup-uv@6b9c6063abd6010835644d4c2e1bef4cf5cd0fca # v6.0.1

- name: Install docs dependencies
run: uv sync --group docs --no-dev

- name: Build MkDocs
run: uv run mkdocs build --strict

# --- Astro (landing page at /) ---
- name: Set up Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: "22"

Comment on lines +51 to +55

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.

- name: Install Astro dependencies
working-directory: site
run: npm ci

- name: Build Astro
working-directory: site
run: npm run build

# --- Merge outputs ---
- name: Merge Astro + MkDocs into final output
run: |
# Guard against Astro accidentally producing a docs/ route
if [ -d "site/dist/docs" ]; then
echo "::error::Astro output contains 'docs/' directory which would overwrite MkDocs output"
exit 1
fi
# Astro output goes to root (use /. to avoid glob edge cases)
cp -r site/dist/. _site/
# MkDocs output is already at _site/docs/ from the build step

# --- Inject preview banner ---
- name: Inject preview banner
env:
PR_NUMBER: ${{ github.event.pull_request.number }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
python3 << 'PYEOF'
import os, pathlib, re
from datetime import datetime, timezone

pr_number = os.environ["PR_NUMBER"]
short_sha = os.environ["HEAD_SHA"][:7]
build_iso = datetime.now(timezone.utc).isoformat()

style = (
"background:linear-gradient(90deg,#f59e0b,#d97706);"
"color:#000;text-align:center;padding:10px 16px;"
"font-weight:600;font-size:14px;position:sticky;"
"top:0;z-index:9999;"
"box-shadow:0 2px 4px rgba(0,0,0,0.15)"
)
time_ago_js = (
"<script>"
"(function(){"
f"var t=new Date('{build_iso}').getTime();"
Comment thread
greptile-apps[bot] marked this conversation as resolved.
Outdated
"function u(){"
"var d=Math.floor((Date.now()-t)/1e3),"
"s=d<60?d+'s ago':d<3600?Math.floor(d/60)+'m ago':"
"d<86400?Math.floor(d/3600)+'h ago':Math.floor(d/86400)+'d ago';"
"document.getElementById('preview-age').textContent=s}"
"u();setInterval(u,30000)"
"})()"
"</script>"
)
banner = (
f'<div style="{style}">'
f"Development Preview &mdash; PR #{pr_number}"
f' &mdash; <code style="background:rgba(0,0,0,0.1);'
f'padding:2px 6px;border-radius:3px;font-size:12px">'
f"{short_sha}</code>"
f' &mdash; built <span id="preview-age"></span>'
f"</div>"
f"{time_ago_js}"
)
count = 0
for f in pathlib.Path("_site").rglob("*.html"):
text = f.read_text(encoding="utf-8")
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")
Comment on lines +124 to +128

Copilot AI Mar 11, 2026

Copy link

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.
PYEOF

- name: Upload preview artifact
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
Comment on lines +131 to +132

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.

with:
name: preview-site
path: _site
retention-days: 5

deploy-preview:
name: Deploy Preview
needs: build
if: github.event.pull_request.head.repo.full_name == github.repository
Comment thread
greptile-apps[bot] marked this conversation as resolved.
runs-on: ubuntu-latest
permissions:
contents: read

Copilot AI Mar 11, 2026

Copy link

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.
pull-requests: write

Copilot AI Mar 11, 2026

Copy link

Choose a reason for hiding this comment

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

This job uses the Issues API (issues.listComments, issues.updateComment, issues.createComment) to manage the PR comment, but the job permissions only grant pull-requests: write. With fine-grained workflow permissions, comment endpoints generally require issues: write; consider adding it here to avoid 403s when posting/updating the preview URL comment.

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

Copilot uses AI. Check for mistakes.

Copilot AI Mar 11, 2026

Copy link

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.
steps:
- name: Download preview artifact
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: preview-site
path: _site

- name: Set up Node.js
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: "22"

- name: Deploy to Cloudflare Pages
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.114.17 > /dev/null
npx wrangler pages deploy _site --project-name=synthorg-pr-preview --branch="pr-${PR_NUMBER}"
Comment on lines +165 to +166

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!


- name: Comment preview URL
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
env:
PREVIEW_URL: "https://pr-${{ github.event.pull_request.number }}.synthorg-pr-preview.pages.dev"
with:
script: |
const url = process.env.PREVIEW_URL;
const headSha = context.payload?.pull_request?.head?.sha || context.sha;
const marker = '<!-- synthorg-pr-preview -->';
const body = [
marker,
'## Preview Deployment',
'',
`**URL:** ${url}`,
'',
`Built from commit \`${headSha.substring(0, 7)}\``,
].join('\n');
Comment thread
coderabbitai[bot] marked this conversation as resolved.

const comments = await github.paginate(
github.rest.issues.listComments,
{
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
per_page: 100,
}
);
const existing = comments.find(
c => c.user?.login === 'github-actions[bot]' && c.body?.includes(marker)
);
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body,
});
}
Comment on lines +138 to +212

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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

What is the latest version of Cloudflare wrangler npm package?

💡 Result:

The latest version of the Cloudflare wrangler npm package is 4.71.0. [1]


Update wrangler to the latest major version or document the reason for staying on 3.x.

Deploy job addresses all previous review feedback:

  • Fork PR guard prevents secret exposure
  • Stable branch alias URL construction (not scraped from output)
  • Unique marker + bot-author check for comment matching
  • Paginated comment listing

However, wrangler is pinned to 3.114.17 while the latest available version is 4.71.0. Using an outdated major version may miss security updates, bug fixes, and new features. Either upgrade to version 4.x or add a comment explaining the reason for pinning to 3.x.

🤖 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 136 - 209, The Deploy to
Cloudflare Pages step currently installs wrangler@3.114.17 (see the run block
that runs "npm i ... wrangler@3.114.17" and then "npx wrangler pages deploy");
update that install to a current 4.x release (e.g., wrangler@4.71.0) and verify
the CLI invocation (npx wrangler pages deploy) still works with v4, or if you
intentionally must stay on 3.x, add an inline comment in the same Deploy to
Cloudflare Pages step explaining the reason for pinning to 3.x
(compatibility/security/regression test) and reference the chosen 3.x version
and a plan/tracker issue for when it will be upgraded.


cleanup-preview:
name: Cleanup Preview
if: >-
github.event.action == 'closed' &&
github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write

Copilot AI Mar 11, 2026

Copy link

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.
steps:
Comment on lines +221 to +224

Copilot AI Mar 11, 2026

Copy link

Choose a reason for hiding this comment

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

This step deletes the preview PR comment via the Issues API (issues.listComments / issues.deleteComment), but the job permissions only include pull-requests: write. Add issues: write at the job level to ensure the token can delete issue comments on PRs.

Copilot uses AI. Check for mistakes.
- name: Delete preview comment
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
script: |
const marker = '<!-- synthorg-pr-preview -->';
const comments = await github.paginate(
github.rest.issues.listComments,
{
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
per_page: 100,
}
);
const existing = comments.find(
c => c.user?.login === 'github-actions[bot]' && c.body?.includes(marker)
);
if (existing) {
await github.rest.issues.deleteComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
});
console.log(`Deleted preview comment ${existing.id}`);
} else {
console.log('No preview comment found');
}

- name: Delete branch deployments
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
BRANCH="pr-${PR_NUMBER}"
# List deployments for this branch (per_page=100 to cover busy PRs)
# Note: Cloudflare prevents deleting the latest deployment per branch — that one will remain
DEPLOYMENTS=$(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}" \
| jq -r ".result[] | select(.deployment_trigger.metadata.branch == \"${BRANCH}\") | .id") || true
Comment thread
greptile-apps[bot] marked this conversation as resolved.
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}..."
Comment on lines +265 to +271

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.

RESPONSE=$(curl -s -w "\n%{http_code}" -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}")
HTTP_CODE=$(echo "$RESPONSE" | tail -1)
if [ "$HTTP_CODE" -ge 400 ]; then
echo "::warning::Failed to delete deployment ${DEPLOY_ID} (HTTP ${HTTP_CODE}) — may be the latest deployment (Cloudflare restriction)"
fi
done
echo "Cleaned up preview deployments for ${BRANCH}"
Comment thread
greptile-apps[bot] marked this conversation as resolved.
8 changes: 8 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,14 @@ src/ai_company/

- **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
- Checks out PR head SHA (not merge commit) so build matches reported commit
- Build job runs regardless (catches build failures); deploy job skips on fork PRs (no secrets access)
- Cleanup job deletes preview comment and Cloudflare deployments on PR close
- Concurrency group cancels stale builds on rapid pushes
- **Docker**: `.github/workflows/docker.yml` — builds backend + web images, pushes to GHCR, signs with cosign. Scans: Trivy (CRITICAL = hard fail, HIGH = warn-only) + Grype (critical cutoff). 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*`).
- **Matrix**: Python 3.14
- **Dependabot**: daily uv + github-actions + docker updates, grouped minor/patch, no auto-merge
Expand Down
1 change: 1 addition & 0 deletions DESIGN_SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -3263,6 +3263,7 @@ synthorg/
│ │ ├── release.yml # Release Please (automated versioning + GitHub Releases)
│ │ ├── secret-scan.yml # Gitleaks on push/PR + weekly
│ │ ├── pages.yml # Build Astro + MkDocs → deploy GitHub Pages
│ │ ├── pages-preview.yml # PR preview → Cloudflare Pages
│ │ └── zizmor.yml # Workflow security analysis (zizmor)
│ ├── actions/
│ │ └── setup-python-uv/ # Composite action: Python + uv install
Expand Down
Loading
Loading