Skip to content

Add manual GitHub Pages republish workflow#212

Merged
FuJacob merged 1 commit into
mainfrom
feat/republish-pages-workflow
May 25, 2026
Merged

Add manual GitHub Pages republish workflow#212
FuJacob merged 1 commit into
mainfrom
feat/republish-pages-workflow

Conversation

@FuJacob
Copy link
Copy Markdown
Owner

@FuJacob FuJacob commented May 25, 2026

Summary

Adds a workflow_dispatch workflow (republish-pages.yml) that re-deploys the Sparkle appcast to GitHub Pages with a configurable custom domain input. This lets us change the Pages CNAME (e.g. from updates.tabbyapp.dev to a new domain) without cutting a full release.

Validation

Workflow syntax validated — uses the same configure-pages, upload-pages-artifact, and deploy-pages actions as the existing release workflow. Cannot be end-to-end tested until merged to a branch with Actions enabled.

Linked issues

Refs #202

Risk / rollout notes

  • No impact on existing release workflow or appcast content.
  • The workflow downloads appcast.xml from the latest successful release run's artifacts, so at least one prior release must exist.
  • Custom domain input defaults to updates.tabbyapp.dev (current production value).

Greptile Summary

This PR adds a workflow_dispatch workflow (republish-pages.yml) that re-deploys the Sparkle appcast to GitHub Pages without cutting a new release, useful when the Pages custom domain changes.

  • The workflow downloads appcast.xml from the latest successful release.yml run's artifacts, then prepares and deploys a Pages artifact with a configurable CNAME — but two logic bugs mean every run would fail or produce wrong output: the artifact extraction path check looks at artifacts/appcast.xml instead of artifacts/build/appcast.xml (matching how upload-artifact preserves the build/ prefix), and the subdirectory is named Cotabby instead of tabby (as used in release.yml), which would drop the /tabby/appcast.xml path from Pages.
  • The inputs.custom_domain value is also interpolated directly into shell in the Summary step rather than passed via an environment variable.

Confidence Score: 2/5

Not safe to merge as-is — two bugs in the new workflow guarantee it either always errors out or deploys a Pages structure that diverges from every prior release.

The artifact path check (artifacts/appcast.xml) will never match the actual extracted location (artifacts/build/appcast.xml), so the workflow will always exit with an error and never publish. Additionally, the subdirectory name Cotabby differs from tabby used by the release workflow; if the path check were fixed, running this workflow would still produce a Pages deployment missing the /tabby/appcast.xml path that existing release artifacts establish. Both issues are on the sole code path this workflow exercises.

.github/workflows/republish-pages.yml — the artifact extraction path and subdirectory name both need correction before this workflow can function correctly.

Security Review

  • Shell injection via inputs.custom_domain (.github/workflows/republish-pages.yml, Summary step): the user-supplied domain input is expanded directly into the shell script by GitHub Actions template substitution before Bash parses it. A value containing shell metacharacters would be executed as code. Risk is bounded to repository collaborators who can trigger workflow_dispatch, but the fix (passing the value through an env: variable) is straightforward.

Important Files Changed

Filename Overview
.github/workflows/republish-pages.yml New manual republish workflow; has two logic bugs (wrong artifact extraction path and mismatched subdirectory name vs release.yml) that would cause every run to fail or produce divergent Pages content, plus a minor shell injection surface in the Summary step.

Sequence Diagram

sequenceDiagram
    actor Operator
    participant GHA as GitHub Actions
    participant GHAPI as GitHub API
    participant Pages as GitHub Pages

    Operator->>GHA: trigger workflow_dispatch (custom_domain input)
    GHA->>GHAPI: "gh run list (release.yml, status=success, limit=1)"
    GHAPI-->>GHA: latest release run ID
    GHA->>GHAPI: gh run download release-artifacts
    GHAPI-->>GHA: artifact zip (build/appcast.xml inside)
    Note over GHA: check artifacts/appcast.xml ❌ actual path: artifacts/build/appcast.xml
    GHA->>GHA: mkdir pages/Cotabby ⚠️ should be pages/tabby
    GHA->>GHA: write CNAME with custom_domain
    GHA->>Pages: configure-pages → upload-pages-artifact → deploy-pages
    Pages-->>GHA: page_url
    GHA->>GHA: write GITHUB_STEP_SUMMARY
Loading

Fix All in Codex Fix All in Claude Code

Reviews (1): Last reviewed commit: "Add manual GitHub Pages republish workfl..." | Re-trigger Greptile

Greptile also left 3 inline comments on this PR.

Adds a workflow_dispatch workflow that re-deploys the Sparkle appcast
to GitHub Pages with a configurable custom domain. Useful for domain
migrations without cutting a new release.
@FuJacob FuJacob merged commit 21569b6 into main May 25, 2026
3 checks passed
Comment on lines +62 to +65
if [ -f artifacts/appcast.xml ]; then
echo "Found appcast.xml from release artifacts"
cp artifacts/appcast.xml appcast.xml
else
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.

P1 The path check looks at artifacts/appcast.xml, but actions/upload-artifact@v4 preserves the workspace-relative directory structure when uploading. In release.yml, the file is staged at build/appcast.xml, so when gh run download --dir artifacts/ extracts the artifact, the file lands at artifacts/build/appcast.xml — not artifacts/appcast.xml. The condition will always be false and the workflow will always exit with the "No appcast.xml found" error.

Suggested change
if [ -f artifacts/appcast.xml ]; then
echo "Found appcast.xml from release artifacts"
cp artifacts/appcast.xml appcast.xml
else
if [ -f artifacts/build/appcast.xml ]; then
echo "Found appcast.xml from release artifacts"
cp artifacts/build/appcast.xml appcast.xml
else

Fix in Codex Fix in Claude Code

Comment on lines +73 to +75
mkdir -p pages/Cotabby
cp appcast.xml pages/appcast.xml
cp appcast.xml pages/Cotabby/appcast.xml
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.

P1 The subdirectory name Cotabby doesn't match the tabby subdirectory that release.yml writes (build/pages/tabby/appcast.xml). Running this workflow would produce a Pages deployment where https://updates.tabbyapp.dev/tabby/appcast.xml no longer exists and a new https://updates.tabbyapp.dev/Cotabby/appcast.xml appears instead — diverging from every prior release deployment.

Suggested change
mkdir -p pages/Cotabby
cp appcast.xml pages/appcast.xml
cp appcast.xml pages/Cotabby/appcast.xml
mkdir -p pages/tabby
cp appcast.xml pages/appcast.xml
cp appcast.xml pages/tabby/appcast.xml

Fix in Codex Fix in Claude Code

Comment on lines +90 to +99
- name: Summary
run: |
{
echo "### Pages Republished"
echo "| Field | Value |"
echo "|-------|-------|"
echo "| Custom domain | ${{ inputs.custom_domain }} |"
echo "| Appcast URL | https://${{ inputs.custom_domain }}/appcast.xml |"
echo "| Pages URL | ${{ steps.deploy.outputs.page_url }} |"
} >> "${GITHUB_STEP_SUMMARY}"
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.

P2 security ${{ inputs.custom_domain }} is expanded directly into the shell script before Bash parses it. Any shell metacharacters in the input (e.g. a value containing "; rm …; #) would be executed. Pass the value through an environment variable instead so Bash never sees it as code.

Suggested change
- name: Summary
run: |
{
echo "### Pages Republished"
echo "| Field | Value |"
echo "|-------|-------|"
echo "| Custom domain | ${{ inputs.custom_domain }} |"
echo "| Appcast URL | https://${{ inputs.custom_domain }}/appcast.xml |"
echo "| Pages URL | ${{ steps.deploy.outputs.page_url }} |"
} >> "${GITHUB_STEP_SUMMARY}"
- name: Summary
env:
CUSTOM_DOMAIN: ${{ inputs.custom_domain }}
PAGES_URL: ${{ steps.deploy.outputs.page_url }}
run: |
{
echo "### Pages Republished"
echo "| Field | Value |"
echo "|-------|-------|"
echo "| Custom domain | ${CUSTOM_DOMAIN} |"
echo "| Appcast URL | https://${CUSTOM_DOMAIN}/appcast.xml |"
echo "| Pages URL | ${PAGES_URL} |"
} >> "${GITHUB_STEP_SUMMARY}"

Fix in Codex Fix in Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant