Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
118 commits
Select commit Hold shift + click to select a range
df28e8e
Add Real system default font option
Shengqiang-Zhang Apr 15, 2026
75fe052
Add workflow to build and publish release APK
Shengqiang-Zhang Apr 15, 2026
fbc6b25
Serve device system font to article WebView
Shengqiang-Zhang Apr 15, 2026
148a2d4
Tighten system font resolution for Samsung and other OEMs
Shengqiang-Zhang Apr 15, 2026
09a50d8
Add custom font option for article WebView
Shengqiang-Zhang Apr 16, 2026
bdb5781
Apply title font to byline and feed name
Shengqiang-Zhang Apr 16, 2026
de5ddf6
Add "Check for updates" to About page using GitHub releases
Shengqiang-Zhang Apr 16, 2026
049fd78
Apply article font app-wide and add scroll-to-top FAB
Shengqiang-Zhang Apr 17, 2026
20caa11
Fix scroll-to-top FAB visibility tracking
Shengqiang-Zhang Apr 17, 2026
0e17e7d
Propagate DB and OPML import failures
Shengqiang-Zhang Apr 17, 2026
6e36832
Reset refresh loading state on failure and cancellation
Shengqiang-Zhang Apr 18, 2026
e648f45
Document Android CLI alongside Gradle in CLAUDE.md
Shengqiang-Zhang Apr 20, 2026
d02f91c
Use image origin as Referer for proxied media requests
Shengqiang-Zhang Apr 20, 2026
e46666e
"Claude PR Assistant workflow"
Shengqiang-Zhang Apr 20, 2026
4059da5
Merge pull request #2 from Shengqiang-Zhang/add-claude-github-actions…
Shengqiang-Zhang Apr 20, 2026
066af5a
fix: Address review comments in WebRequestProxyPolicy
github-actions[bot] Apr 20, 2026
8010fba
Merge pull request #1 from Shengqiang-Zhang/fix/media-proxy-referer
Shengqiang-Zhang Apr 20, 2026
5d7e3a8
Merge branch 'main' of https://github.com/Shengqiang-Zhang/capyreader
Shengqiang-Zhang Apr 20, 2026
f24e1f2
add the github action copilot-claude PR review-fix loop
Shengqiang-Zhang Apr 21, 2026
ebd7b5f
ci: tailor Copilot auto-fix prompt to capyreader toolchain
Shengqiang-Zhang Apr 21, 2026
59bd207
ci: replace Flutter check commands with capyreader toolchain
Shengqiang-Zhang Apr 21, 2026
fbbcbbd
fix(webview): Reconcile image load state and close leaked Responses
Shengqiang-Zhang Apr 21, 2026
2d2dcab
fix: address Copilot review feedback on encoding inference for text M…
Shengqiang-Zhang Apr 21, 2026
1a76188
ci(copilot-loop): cap Claude step at 30m and skip Gradle on the runner
Shengqiang-Zhang Apr 21, 2026
88294fa
fix: address Copilot review feedback on WebView response handling
Shengqiang-Zhang Apr 21, 2026
310179b
fix: address Copilot review feedback on image listeners and workflow …
Shengqiang-Zhang Apr 21, 2026
7c9eba1
Revert "fix: address Copilot review feedback on image listeners and w…
Shengqiang-Zhang Apr 21, 2026
87736ff
fix: address Copilot review feedback on image load listeners
Shengqiang-Zhang Apr 21, 2026
82519de
Merge pull request #3 from Shengqiang-Zhang/fix/webview-image-load-re…
Shengqiang-Zhang Apr 21, 2026
989ae3a
feat(web): scaffold Vite + React + TS web companion with Miniflux API…
Shengqiang-Zhang Apr 22, 2026
111aec7
feat(web): three-pane reader shell with resizable panels, sidebar, vi…
Shengqiang-Zhang Apr 22, 2026
423dd7d
feat(web): optimistic read/unread + star mutations synced to Miniflux
Shengqiang-Zhang Apr 22, 2026
9e6de41
feat(web): article rendering parity with Android via sandboxed iframe
Shengqiang-Zhang Apr 22, 2026
bc8aff1
feat(web): keyboard shortcuts and search
Shengqiang-Zhang Apr 22, 2026
273c9ba
feat(web): subscription management with Add Feed + OPML import/export
Shengqiang-Zhang Apr 22, 2026
549f332
feat(web): Azure Static Web Apps deployment workflow
Shengqiang-Zhang Apr 22, 2026
f6e2c27
docs(web): add Heroku deployment guide for Miniflux using Student Pack
Shengqiang-Zhang Apr 22, 2026
8283df2
docs(web): correct migration order — Capy Reader Android is single-ac…
Shengqiang-Zhang Apr 22, 2026
fa0b95e
fix(web): pick up cross-device writes without a hard reload
Shengqiang-Zhang Apr 22, 2026
6c69cdb
fix(ci): stage only dist/ for SWA upload to avoid 250 MiB content cap
Shengqiang-Zhang Apr 22, 2026
83a8ecc
fix: address Copilot review feedback on blob URL revocation
Shengqiang-Zhang Apr 22, 2026
9e0cc8d
fix: address Copilot review feedback on ShortcutsDialog duplication
Shengqiang-Zhang Apr 22, 2026
e0805bb
fix: address Copilot review feedback on mutation cache invalidation
Shengqiang-Zhang Apr 22, 2026
bec3008
fix: address Copilot review feedback on iframe security
Shengqiang-Zhang Apr 22, 2026
33f18c7
fix: address Copilot review feedback on stale empty-state copy
Shengqiang-Zhang Apr 22, 2026
33f8de2
fix: address Copilot review feedback on dialog accessibility and entr…
Shengqiang-Zhang Apr 22, 2026
1c4c116
Merge pull request #4 from Shengqiang-Zhang/feat/web-companion
Shengqiang-Zhang Apr 22, 2026
5040a32
feat(web): per-reader font settings for the article pane
Shengqiang-Zhang Apr 22, 2026
d8b7c04
Merge pull request #5 from Shengqiang-Zhang/feat/web-article-font-set…
Shengqiang-Zhang Apr 22, 2026
d335e1f
feat(web): allow arbitrary system fonts in article appearance
Shengqiang-Zhang Apr 22, 2026
a25833b
Merge pull request #6 from Shengqiang-Zhang/feat/web-custom-system-fonts
Shengqiang-Zhang Apr 22, 2026
e97e2f5
fix(web): article reader bugs — unread toggle, hotlink images, load f…
Shengqiang-Zhang Apr 22, 2026
bc55d5a
fix(web): discard stale fetch-content response after entry switch
github-actions[bot] Apr 23, 2026
4a89248
ci(web): split web deploy into push- and PR-scoped workflows
Shengqiang-Zhang Apr 23, 2026
250753f
Merge pull request #8 from Shengqiang-Zhang/fix/web-deploy-split-work…
Shengqiang-Zhang Apr 23, 2026
9e317fd
feat(web): add "Mark above as read" per-article action
Shengqiang-Zhang Apr 23, 2026
3cefe71
fix(web): resolve Miniflux /proxy/ image urls so hotlink-blocked CDNs…
Shengqiang-Zhang Apr 23, 2026
95f8b9b
fix(web): parse srcset with spec-compliant parser to avoid splitting …
github-actions[bot] Apr 23, 2026
cf717a2
fix(web): disable Mark above as read when no unread entries exist above
github-actions[bot] Apr 23, 2026
b2fddc1
Merge pull request #10 from Shengqiang-Zhang/fix/web-miniflux-proxy-r…
Shengqiang-Zhang Apr 23, 2026
51fb623
Merge pull request #9 from Shengqiang-Zhang/feat/web-mark-above-as-read
Shengqiang-Zhang Apr 23, 2026
7bdcd66
docs(spec): codex ↔ claude review-fix loop design
Shengqiang-Zhang Apr 23, 2026
567c9ec
docs(plan): codex ↔ claude review-fix loop implementation
Shengqiang-Zhang Apr 23, 2026
203b4bd
ci: add codex ↔ claude review-fix loop workflow
Shengqiang-Zhang Apr 23, 2026
0850ca0
ci: disable copilot fix-loop trigger; codex loop is now primary
Shengqiang-Zhang Apr 23, 2026
6bcb948
docs: add codex ↔ claude review loop setup guide
Shengqiang-Zhang Apr 23, 2026
35bfb12
docs(copilot-loop): mark loop disabled; codex loop is now primary
Shengqiang-Zhang Apr 23, 2026
9ada05a
Merge pull request #11 from Shengqiang-Zhang/ci/codex-claude-review-loop
Shengqiang-Zhang Apr 23, 2026
9f0722c
fix(web): set iframe <base href> to Miniflux origin so all article im…
Shengqiang-Zhang Apr 23, 2026
7657556
fix(web): prevent href="#" links from opening new tab via base href
github-actions[bot] Apr 23, 2026
a6f2e82
Merge pull request #12 from Shengqiang-Zhang/fix/web-iframe-base-href…
Shengqiang-Zhang Apr 24, 2026
8aa51a3
fix(web): surface real feed-load errors and offer recovery actions
Shengqiang-Zhang Apr 24, 2026
7588940
Merge pull request #13 from Shengqiang-Zhang/fix/web-feed-load-error-…
Shengqiang-Zhang Apr 24, 2026
148fb4c
fix(web): retry hotlink-blocked images via configurable fallback proxy
Shengqiang-Zhang Apr 25, 2026
978f929
fix: address Codex review feedback on image fallback src resolution
Shengqiang-Zhang Apr 25, 2026
9063b4c
fix: address Codex review feedback on proxy-skip origin scoping
Shengqiang-Zhang Apr 25, 2026
8444d5a
fix: address Codex review feedback on Miniflux proxy subpath detection
Shengqiang-Zhang Apr 25, 2026
43b0142
Merge pull request #14 from Shengqiang-Zhang/fix/web-image-fallback-p…
Shengqiang-Zhang Apr 25, 2026
fdbb0a6
fix(web): rewrite Miniflux proxy URLs anchored at localhost
Shengqiang-Zhang Apr 25, 2026
ddb296e
Merge pull request #15 from Shengqiang-Zhang/fix/web-rewrite-localhos…
Shengqiang-Zhang Apr 25, 2026
3b0fbca
fix(web): retry Miniflux proxy errors via fallback with decoded upstr…
Shengqiang-Zhang Apr 25, 2026
ca1a59d
fix: address Codex review feedback on UTF-8 decoding of Miniflux prox…
Shengqiang-Zhang Apr 25, 2026
5e8148e
Merge pull request #16 from Shengqiang-Zhang/fix/web-image-fallback-d…
Shengqiang-Zhang Apr 25, 2026
53a6b29
fix(android): resolve Miniflux /proxy/ image urls so hotlink-blocked …
Shengqiang-Zhang Apr 26, 2026
b287dde
fix: address Codex review feedback on subfolder Miniflux deployment p…
Shengqiang-Zhang Apr 26, 2026
f76ea22
Merge pull request #17 from Shengqiang-Zhang/fix/android-resolve-mini…
Shengqiang-Zhang Apr 26, 2026
e5b26e4
fix(android): retry Miniflux proxy image errors via decoded upstream URL
Shengqiang-Zhang Apr 27, 2026
2dd8a85
fix(android): also retry image load when src is a relative /proxy/ path
Shengqiang-Zhang Apr 27, 2026
5558414
fix: address Codex review feedback on gallery staleness and srcset fa…
Shengqiang-Zhang Apr 27, 2026
98614c3
Merge pull request #18 from Shengqiang-Zhang/fix/android-miniflux-pro…
Shengqiang-Zhang Apr 27, 2026
c554fea
fix(web): scroll to anchor article after "mark above as read"
Shengqiang-Zhang May 11, 2026
68b4584
fix: address Codex review feedback on mark-above scroll in All filter
Shengqiang-Zhang May 11, 2026
987c5fc
fix: address Codex review feedback on full-page scroll anchor guard
Shengqiang-Zhang May 11, 2026
59334bd
fix: address Codex review feedback on stale mark-above anchor guard
Shengqiang-Zhang May 11, 2026
db322c6
fix: address Codex review feedback on optimistic-update anchor guard
Shengqiang-Zhang May 11, 2026
eae591f
fix: address Codex review feedback on All-filter anchor cleanup
Shengqiang-Zhang May 11, 2026
a5696e4
Merge pull request #19 from Shengqiang-Zhang/fix/web-scroll-to-anchor…
Shengqiang-Zhang May 11, 2026
f4a4107
feat(web): add copy-to-clipboard icon for feed URLs in Manage Feeds
Shengqiang-Zhang May 15, 2026
81d10ef
Merge pull request #20 from Shengqiang-Zhang/feat/web-copy-feed-url-icon
Shengqiang-Zhang May 15, 2026
1acd068
fix(android): scroll article list to top after pull-to-refresh
Shengqiang-Zhang May 15, 2026
f02fafe
Merge pull request #21 from Shengqiang-Zhang/fix/android-scroll-to-to…
Shengqiang-Zhang May 15, 2026
e49b0c5
feat(web): allow moving feeds between categories from Manage feeds di…
Shengqiang-Zhang May 15, 2026
bfa2d10
feat(web): keep an article in the Unread list after clicking it
Shengqiang-Zhang May 15, 2026
d37545a
feat(web): show real feed favicons and per-feed tint in sidebar + art…
Shengqiang-Zhang May 15, 2026
aedc375
fix(web): invalidate entries when marking unread so Unread view stays…
github-actions[bot] May 15, 2026
924667e
fix(web): merge copy-URL feature from main into ManageFeedsDialog
github-actions[bot] May 15, 2026
45c4c79
fix(web): revert manual copy-URL integration to clear merge conflict
github-actions[bot] May 15, 2026
8a18440
Merge pull request #23 from Shengqiang-Zhang/feat/web-feed-favicons-t…
Shengqiang-Zhang May 15, 2026
2a3ac93
Merge remote-tracking branch 'origin/main' into feat/web-feed-move-an…
Shengqiang-Zhang May 15, 2026
00a456c
fix(web): evict inactive entry caches when marking read
Shengqiang-Zhang May 15, 2026
fa38678
fix: address Codex review feedback on active unread cache expiry
Shengqiang-Zhang May 15, 2026
e9bfada
fix: address Codex review feedback on read-status invalidation assertion
Shengqiang-Zhang May 15, 2026
1aa77bb
fix: address Codex review feedback on active unread cache and window-…
Shengqiang-Zhang May 15, 2026
2e951ec
fix: address Codex review feedback on unread-cache test observer
Shengqiang-Zhang May 15, 2026
9a763be
fix(web): make unread-cache test observer truly active
Shengqiang-Zhang May 15, 2026
ae635f8
Merge pull request #22 from Shengqiang-Zhang/feat/web-feed-move-and-k…
Shengqiang-Zhang May 15, 2026
9507744
add .claude/worktrees to gitignore
Shengqiang-Zhang May 15, 2026
e231e4e
Merge upstream jocmp/capyreader main
Shengqiang-Zhang May 15, 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
52 changes: 52 additions & 0 deletions .github/CODEX_LOOP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Codex ↔ Claude review loop

Mirrors the (now-disabled) Copilot ↔ Claude loop documented in `COPILOT_LOOP.md`, but driven by the ChatGPT Codex Connector instead of GitHub Copilot.

## Files involved

- `.github/workflows/claude.yml` — generic `@claude` mention responder (shared across loops).
- `.github/workflows/claude-fix-codex-review.yml` — fires when Codex submits a review; Claude addresses the feedback and pushes back to the PR branch.

There is no `setup-codex-loop.yml` analog: Codex auto-review-on-push is configured in the Codex web UI, not via a GitHub ruleset.

## One-time setup per repo

1. **Create two secrets** in *Settings → Secrets and variables → Actions*:
- `CLAUDE_CODE_OAUTH_TOKEN` — from `claude setup-token` (or the Claude Code install flow).
- `CODEX_LOOP_PAT` — a fine-grained Personal Access Token scoped to just this repo with:
- Contents: **Read and write**
- Pull requests: **Read and write**

This PAT can be the same value as `COPILOT_LOOP_PAT` if that already exists — the permissions are identical. Two distinct secret names just keep the two loops independently configurable.

2. **Enable Codex auto-review on push** in the Codex web UI for this repo. Without that setting, the loop terminates after one round because nothing re-triggers Codex on Claude's push.

3. Open a PR; once Codex has reviewed it once, every subsequent push triggers the next Codex round automatically (until Codex has no feedback left, or the iteration cap is hit).

## Why a PAT?

Defensive design. The Copilot version uses a PAT because GitHub's `copilot_code_review` ruleset silently ignores `github-actions[bot]` pushes (verified empirically — bot pushes produce no `review_requested` event). It's not yet confirmed whether Codex's auto-review setting has the same restriction. If observation across the first few PRs shows Codex re-fires on bot pushes, the PAT can be dropped and the workflow simplified to use the default `GITHUB_TOKEN`.

## Iteration cap

`claude-fix-codex-review.yml` has `MAX_CODEX_ITERATIONS: 7`. After that many Codex reviews on a single PR, Claude posts a comment and stops auto-fixing so humans can take over. Bump the env var if you want more rounds.

## Project-specific check commands

The prompt in `claude-fix-codex-review.yml` mentions Capy-specific check commands (`make` and `make check` for `.js`/`.liquid` changes; skip `./gradlew` because no Android SDK is installed on the runner). When copying to another repo, edit that paragraph to point at the destination repo's lightweight check commands.

## Coexistence with the Copilot loop

`claude-fix-copilot-review.yml` is still on disk but its `on:` trigger has been neutered to `workflow_dispatch:` only — it will not fire on PR events. To re-enable Copilot, restore the original `pull_request_review:` trigger.

The GitHub-side `copilot_code_review` ruleset created by `setup-copilot-loop.yml` still exists and may still cause Copilot to post reviews on every push. Disabling the Claude-fix workflow stops Claude from acting on those reviews, but does not stop Copilot from posting them. To fully retire Copilot:

```bash
# List rulesets to find the id
gh api "repos/<owner>/<repo>/rulesets"

# Delete the copilot_code_review ruleset (use the id from above)
gh api --method DELETE "repos/<owner>/<repo>/rulesets/<ruleset-id>"
```

Or disable it via the GitHub UI (Settings → Rules → Rulesets → "Copilot auto review for all branches" → Disable).
34 changes: 34 additions & 0 deletions .github/COPILOT_LOOP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Copilot ↔ Claude review loop

> **Status (2026-04-23):** This loop is **disabled** in this repo — the active review-fix loop is now `CODEX_LOOP.md`. The workflow file `claude-fix-copilot-review.yml` has had its `on:` trigger neutered to `workflow_dispatch:` only. To re-enable, restore the original `pull_request_review` trigger.

Copy these three workflows into another repo and the loop works after a one-time secret + setup-workflow step. No GitHub web-UI settings, no per-repo file edits.

## Files to copy

- `.github/workflows/claude.yml` — responds to `@claude` mentions in issues / PR comments.
- `.github/workflows/claude-fix-copilot-review.yml` — fires when Copilot submits a review; Claude fixes the feedback and pushes back to the PR branch.
- `.github/workflows/setup-copilot-loop.yml` — one-shot, creates the `copilot_code_review` ruleset so pushes by the PAT owner auto-trigger the next Copilot review.

## One-time setup per repo

1. **Create two secrets** in *Settings → Secrets and variables → Actions*:
- `CLAUDE_CODE_OAUTH_TOKEN` — from `claude setup-token` (or the Claude Code install flow).
- `COPILOT_LOOP_PAT` — a fine-grained Personal Access Token scoped to just this repo with:
- Contents: **Read and write**
- Pull requests: **Read and write**
- Administration: **Read and write** — this is the permission that covers creating/updating rulesets. Only needed while running `setup-copilot-loop`; you can remove it afterward and regenerate the PAT with just Contents + Pull requests.
2. **Run the setup workflow once.** Actions tab → *Setup Copilot ↔ Claude loop* → *Run workflow*. It creates/updates the ruleset via the REST API.
3. Open a PR and request a Copilot review on it once — from then on, every Claude push triggers the next Copilot round until Copilot has no feedback left (or the iteration cap is hit).

## Why the PAT is unavoidable

GitHub's `copilot_code_review` ruleset silently ignores pushes authenticated as `github-actions[bot]` (verified empirically on PR #21, 2026-04-20 — bot pushes produced no `review_requested` event, user pushes did). The REST endpoint `POST /pulls/{n}/requested_reviewers` with `copilot-pull-request-reviewer` is not a reliable fallback either. So the loop depends on Claude's push being attributed to a real user; the PAT provides that identity. Everything *else* (the ruleset, the git author config) is automated by the workflows.

## Iteration cap

`claude-fix-copilot-review.yml` has `MAX_COPILOT_ITERATIONS: 5`. After that many Copilot reviews on a single PR, Claude posts a comment and stops auto-fixing so humans can take over. Bump the env var if you want more rounds.

## Project-specific check commands

The prompt in `claude-fix-copilot-review.yml` mentions `ego_sdk/flutter/bin/flutter analyze` / `flutter test` as the checks Claude should run before committing. When copying to another repo, edit that single `run the project checks` paragraph to point at the destination repo's build/test commands. Nothing else in the workflow is repo-specific.
106 changes: 106 additions & 0 deletions .github/workflows/build-release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
name: Build and Publish Release APK

on:
workflow_dispatch:
inputs:
tag_name:
description: "Release tag (e.g. v2026.04.1). Leave blank to derive one from the date + run number."
required: false
type: string
prerelease:
description: "Mark as prerelease"
required: false
type: boolean
default: false
push:
tags:
- "v*"

concurrency:
group: build-release-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: write

jobs:
build:
name: Build free release APK
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Set up Java
uses: actions/setup-java@v4
with:
distribution: "zulu"
java-version: "21"

- name: Set up Gradle
uses: gradle/actions/setup-gradle@v4

- name: Resolve tag name
id: tag
run: |
if [ -n "${{ github.event.inputs.tag_name }}" ]; then
TAG="${{ github.event.inputs.tag_name }}"
elif [ "${{ github.ref_type }}" = "tag" ]; then
TAG="${{ github.ref_name }}"
else
TAG="v$(date -u +'%Y.%m.%d')-build.${{ github.run_number }}"
fi
echo "name=${TAG}" >> "$GITHUB_OUTPUT"
echo "Resolved tag: ${TAG}"

- name: Configure signing
env:
ENCODED_RELEASE_KEYSTORE: ${{ secrets.ENCODED_RELEASE_KEYSTORE }}
ENCODED_SECRETS_PROPERTIES: ${{ secrets.ENCODED_SECRETS_PROPERTIES }}
run: |
if [ -n "${ENCODED_RELEASE_KEYSTORE}" ] && [ -n "${ENCODED_SECRETS_PROPERTIES}" ]; then
echo "Using release keystore from repository secrets"
printf '%s' "${ENCODED_RELEASE_KEYSTORE}" | base64 --decode > ./release.keystore
printf '%s' "${ENCODED_SECRETS_PROPERTIES}" | base64 --decode > ./secrets.properties
else
echo "No release secrets found — falling back to committed debug.keystore"
cp debug.keystore release.keystore
printf '%s\n' 'store_password=android' 'key_alias=androiddebugkey' 'key_password=android' > secrets.properties
fi

- name: Build free release APK
run: ./gradlew :app:assembleFreeRelease --no-daemon --stacktrace

- name: Stage APK artifact
id: artifact
run: |
SRC="$(ls app/build/outputs/apk/free/release/app-free-release*.apk | head -n 1)"
if [ -z "${SRC}" ] || [ ! -f "${SRC}" ]; then
echo "APK not found in app/build/outputs/apk/free/release/" >&2
ls -R app/build/outputs/apk || true
exit 1
fi
mkdir -p release-artifacts
DEST="release-artifacts/capyreader-${{ steps.tag.outputs.name }}.apk"
cp "${SRC}" "${DEST}"
echo "path=${DEST}" >> "$GITHUB_OUTPUT"

- name: Upload APK workflow artifact
uses: actions/upload-artifact@v4
with:
name: capyreader-${{ steps.tag.outputs.name }}
path: ${{ steps.artifact.outputs.path }}
if-no-files-found: error

- name: Publish GitHub Release
uses: softprops/action-gh-release@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ steps.tag.outputs.name }}
name: ${{ steps.tag.outputs.name }}
draft: false
prerelease: ${{ github.event.inputs.prerelease == 'true' }}
generate_release_notes: true
fail_on_unmatched_files: true
files: ${{ steps.artifact.outputs.path }}
153 changes: 153 additions & 0 deletions .github/workflows/claude-fix-codex-review.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
name: Claude Fix Codex Review

# The fix half of the Codex ↔ Claude review loop. Fires whenever the ChatGPT
# Codex Connector submits a review, lets Claude address the feedback, and
# pushes the fixes back to the PR branch. The push is attributed to the
# CODEX_LOOP_PAT owner so that Codex's "auto-review on push" UI setting
# re-fires the next round of review (it's not yet known whether Codex
# auto-review fires on github-actions[bot] pushes — using the PAT is the
# safe default; can be simplified later if observation confirms bot-push
# auto-review works).
#
# Portability: no repo-specific values here other than the project-specific
# pre-commit checks paragraph in the prompt. See .github/CODEX_LOOP.md for
# setup instructions.

on:
pull_request_review:
types: [submitted]

env:
# Hard cap on Codex review iterations per PR. Once Codex has submitted
# this many reviews, Claude stops auto-fixing and asks for manual review.
MAX_CODEX_ITERATIONS: 7

jobs:
fix-codex-review:
# Only run when the ChatGPT Codex Connector is the reviewer, and skip
# fork PRs — we need write access to the PR branch to push fixes.
if: |
github.event.review.user.login == 'chatgpt-codex-connector[bot]' &&
github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
issues: read
id-token: write
actions: read
steps:
- name: Verify CODEX_LOOP_PAT secret is configured
env:
CODEX_LOOP_PAT: ${{ secrets.CODEX_LOOP_PAT }}
run: |
if [ -z "${CODEX_LOOP_PAT:-}" ]; then
echo "::error::CODEX_LOOP_PAT secret is missing. Add it — see .github/CODEX_LOOP.md." >&2
exit 1
fi

- name: Check Codex iteration cap
id: cap
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
count=$(gh api --paginate \
"repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/reviews" \
--jq '[.[] | select(.user.login == "chatgpt-codex-connector[bot]")] | length' \
| awk '{s+=$1} END {print s+0}')
echo "Codex reviews so far: $count (cap: $MAX_CODEX_ITERATIONS)"
if [ "$count" -ge "$MAX_CODEX_ITERATIONS" ]; then
gh pr comment ${{ github.event.pull_request.number }} --body \
"Claude auto-fix stopped after $count Codex review iterations (cap: $MAX_CODEX_ITERATIONS). Remaining feedback needs manual attention."
echo "skip=true" >> "$GITHUB_OUTPUT"
else
echo "skip=false" >> "$GITHUB_OUTPUT"
fi

- name: Checkout PR head branch
if: steps.cap.outputs.skip != 'true'
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.ref }}
fetch-depth: 0
# Push as the PAT owner (not github-actions[bot]) so that Codex's
# auto-review-on-push setting fires reliably on the next push.
token: ${{ secrets.CODEX_LOOP_PAT }}

- name: Derive git identity from PAT owner
if: steps.cap.outputs.skip != 'true'
# Attribute commits to the PAT owner via their GitHub noreply email so
# they show up as regular user-authored commits on the PR timeline.
# Derived at runtime so this file is portable across repos/owners.
env:
GH_TOKEN: ${{ secrets.CODEX_LOOP_PAT }}
run: |
set -euo pipefail
owner_json=$(gh api /user)
login=$(jq -r '.login' <<<"$owner_json")
id=$(jq -r '.id' <<<"$owner_json")
name=$(jq -r '.name // .login' <<<"$owner_json")
git config user.name "$name"
git config user.email "${id}+${login}@users.noreply.github.com"

# We intentionally use the sub-path `base-action` instead of the
# top-level `anthropics/claude-code-action@v1`. The top-level action's
# `checkHumanActor` unconditionally calls `GET /users/<actor>` before
# consulting `allowed_bots`, and that crashes for some bot logins. The
# base-action skips all actor/permission checks and just runs Claude
# Code with our prompt — the job-level `if:` already restricts execution
# to Codex reviews on same-repo PRs.
- name: Run Claude Code to address Codex review
if: steps.cap.outputs.skip != 'true'
uses: anthropics/claude-code-action/base-action@v1
timeout-minutes: 30
env:
# Use the PAT so Claude's `gh api` calls (inline review replies,
# PR comments, etc.) are attributed to the PAT owner instead of
# github-actions[bot]. Matches the git push identity set above.
GH_TOKEN: ${{ secrets.CODEX_LOOP_PAT }}
GITHUB_TOKEN: ${{ secrets.CODEX_LOOP_PAT }}
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
prompt: |
The ChatGPT Codex Connector just submitted a review on PR #${{ github.event.pull_request.number }}
(review id ${{ github.event.review.id }}). Address its feedback so that the Codex ↔ Claude
review-fix loop can continue until Codex has no more issues.

1. Fetch the review body and all inline comments attached to it:
gh api repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/reviews/${{ github.event.review.id }}
gh api --paginate repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/comments
Only act on inline comments whose `pull_request_review_id` equals ${{ github.event.review.id }}.

2. For each comment, decide whether the suggestion is valid:
- If it is a legitimate bug, code smell, or style issue, fix it by editing the code on the PR branch.
- If it is a false positive, leave the code alone and reply to that specific comment explaining why.

3. Before committing, run ONLY the cheap checks that can realistically finish in this
runner (no Android SDK is installed and no Gradle cache is warmed, so a cold
`./gradlew assembleFreeDebug` will hang for tens of minutes downloading SDK
components and typically fail on license acceptance — do NOT run it here).
- Kotlin/Android changes: rely on the repo's existing `test.yml` CI workflow to
validate the build after you push. Do not invoke `./gradlew` from this job.
Limit yourself to static reasoning (read the code, check types by inspection)
and, at most, `./gradlew help` or `./gradlew :<module>:tasks --offline` as a
sanity check — and even those only if you can confirm the Gradle daemon starts
within a minute. If in doubt, skip the check entirely and push; CI will catch it.
- `.js` / `.liquid` asset changes: run `make` to recompile assets, then `make check`
to typecheck. These do not need the Android SDK and are safe to run here.
If you are running this workflow on a different repo, substitute the project's own
lightweight check commands — never a full Android build.

4. Commit each logical group of fixes with a message like
`fix: address Codex review feedback on <topic>` and push to the PR branch.
Do not force-push, do not amend existing commits, and do not resolve review conversations.

5. Do NOT manually request a Codex re-review. Codex's "auto-review on push" UI
setting handles re-triggering when the next push lands. If you pushed nothing
(all comments were false positives or the review was purely approving), the loop
terminates naturally — do not post an empty review or comment.

A workflow-level cap (MAX_CODEX_ITERATIONS = ${{ env.MAX_CODEX_ITERATIONS }}) will stop
this job from running after too many Codex review rounds, so you do not need to track that yourself.
claude_args: '--allowed-tools Bash(gh:*),Bash(git:*),Bash(make:*),Edit,Write,Read,Grep,Glob'
Loading