-
Notifications
You must be signed in to change notification settings - Fork 0
355 lines (335 loc) · 19.2 KB
/
Copy pathqueue_filing.yml
File metadata and controls
355 lines (335 loc) · 19.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
name: Queue filing
# The dashboard's 📥 `queue-intake` / 📑 `queue-cite` buttons open prefilled
# issues — from the reading queue or from either suggestion tier
# (`arxiv-inbox.md`, `arxiv-interests.md`), which the issue's `file:` names;
# this workflow has Claude file the named paper — bibliography entry,
# wiki sources section (minimal for cite, full stub for intake, the human's
# notes folded in), DONE-mark on the queue line — and opens a reviewable PR
# that closes the issue. Merge stays human.
#
# Two doors. `labeled` is the dashboard path: GitHub fires it both for labels
# applied at creation and for labels added later, and using it alone avoids
# the double-run an `opened`+`labeled` pair would cause. `workflow_dispatch`
# on an issue number is the hand-over from queue_actions.yml for an issue
# whose label the issue form dropped (a `queue intake: …` title with no
# label at all — it happens on phone submissions), and the door the nightly
# queue_sweep.yml re-dispatches through. Only owner/member/collaborator-
# authored issues act (public repo — same gate as queue_actions.yml), and
# only triage+ users can apply labels at all; the dispatch door re-checks
# the association from the API, since it has no event to read it from.
#
# The PR is created with the runner's GITHUB_TOKEN, so `pull_request`
# workflows (validate.yml) do NOT run on it — `make validate` + pytest run
# here as the gate instead, before anything is pushed.
#
# **Opening the PR needs a token that is allowed to.** GITHUB_TOKEN may open
# a PR only when "Allow GitHub Actions to create and approve pull requests"
# is on (Settings → Actions → General → Workflow permissions — and when the
# organisation enforces that setting the repo page does not even show it).
# So the PR step prefers a `QUEUE_PR_TOKEN` repo secret when one exists: a
# fine-grained personal access token scoped to this repo with *Pull
# requests: read and write* (plus the metadata read it gets by default).
# A PR opened with it also runs validate.yml, which a GITHUB_TOKEN-created
# PR never does. Without either, the filing still lands on its
# `queue-filing/issue-<n>` branch, gated, and the issue gets a one-tap
# compare link instead of a PR — but nothing reaches `main` until a human
# opens and merges it, and the board's *Filings awaiting merge* section is
# where those branches wait. Six did between 2026-09-01 and 2026-09-11.
#
# A second tap on the same paper does not file it twice: a filing branch
# that already exists for this issue, or for another open issue naming the
# same paper, is answered with its compare link and the run stops clean.
#
# Claude runs with tools scoped to workspace edits plus arXiv lookups; the
# deterministic steps below stage explicit paths only — never `git add -A`.
on:
issues:
types: [labeled]
workflow_dispatch:
inputs:
issue:
description: "Issue number to file — the hand-over and sweep door"
required: true
type: string
# contents: write → push the filing branch; pull-requests/issues: write → the
# PR + failure comment; id-token: write → claude-code-action's OIDC startup
# (same requirement as PyAutoMind's arxiv_papers.yml).
permissions:
contents: write
pull-requests: write
issues: write
id-token: write
concurrency:
group: queue-filing-${{ github.event.issue.number || inputs.issue }}
cancel-in-progress: false
jobs:
file-paper:
if: >-
github.event_name == 'workflow_dispatch' ||
(contains(fromJSON('["queue-intake", "queue-cite"]'),
github.event.label.name) &&
contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'),
github.event.issue.author_association))
runs-on: ubuntu-latest
env:
GH_TOKEN: ${{ github.token }}
# gh needs explicit repo context in the pre-checkout secret-guard step.
GH_REPO: ${{ github.repository }}
ISSUE: ${{ github.event.issue.number || inputs.issue }}
steps:
- name: Require the Claude OAuth token
env:
TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
run: |
if [ -z "$TOKEN" ]; then
echo "::error::CLAUDE_CODE_OAUTH_TOKEN is not set on this repo — add it under Settings → Secrets and variables → Actions to enable queue filing."
gh issue comment "$ISSUE" --body \
"Queue filing is not enabled yet: the CLAUDE_CODE_OAUTH_TOKEN secret is missing on this repo (Settings → Secrets and variables → Actions). File this paper from a Claude Code session instead, or add the secret and re-apply the label."
exit 1
fi
- uses: actions/checkout@v4
# The board tests import the family look from the Brain
# (PyAutoBrain/board/_theme.py — moved there in dcd1e2c, which added
# this step to validate.yml and knowledge_board.yml but not here).
# Without it the gate's pytest — and Claude's own in-prompt gate, which
# is told to fix its own mistakes until green but cannot fix a missing
# checkout — dies on 30 tests/test_board.py errors after the filing
# work is already done. So it lands before the Claude step, not just
# before the gate.
- uses: actions/checkout@v4
with:
repository: PyAutoLabs/PyAutoBrain
path: PyAutoBrain
- uses: actions/setup-python@v5
with:
python-version: "3.12"
# The gate below (and Claude's own in-sandbox check) runs pytest; the
# bare runner doesn't ship it — its absence failed the first live run.
- name: Install the test runner
run: pip install pytest
# Read the issue back from the API rather than the event: the dispatch
# door has no event payload, and one code path for both doors is the
# point. `label` is the filing label the issue carries, or the one its
# `queue intake:` / `queue cite:` title says the form dropped.
- name: Resolve the request
id: resolve
run: |
issue_json=$(gh api "repos/${GITHUB_REPOSITORY}/issues/${ISSUE}")
state=$(jq -r '.state' <<<"$issue_json")
assoc=$(jq -r '.author_association' <<<"$issue_json")
title=$(jq -r '.title' <<<"$issue_json")
labels=$(jq -r '[.labels[].name] | join(",")' <<<"$issue_json")
# env, never inline interpolation — issue text is untrusted input.
jq -r '.body // ""' <<<"$issue_json" > /tmp/issue_body.txt
printf '%s' "$title" > /tmp/issue_title.txt
skip=no
if [ "$state" != "open" ]; then
echo "issue #${ISSUE} is ${state} — nothing to file"; skip=yes
fi
case "$assoc" in
OWNER|MEMBER|COLLABORATOR) ;;
*) echo "issue #${ISSUE} author is ${assoc} — not acting"; skip=yes ;;
esac
label="${{ github.event.label.name }}"
if [ -z "$label" ]; then
case ",$labels," in
*,queue-intake,*) label=queue-intake ;;
*,queue-cite,*) label=queue-cite ;;
esac
fi
if [ -z "$label" ]; then
case "$title" in
"queue intake:"*) label=queue-intake ;;
"queue cite:"*) label=queue-cite ;;
esac
if [ -n "$label" ]; then
gh issue edit "$ISSUE" --add-label "$label" || true
echo "issue #${ISSUE} carried no label — read '${label}' from its title"
fi
fi
if [ -z "$label" ]; then
echo "issue #${ISSUE} is not a filing request (title: ${title}) — nothing to do"
skip=yes
fi
printf '%s' "$label" > /tmp/issue_label.txt
echo "label=${label}" >> "$GITHUB_OUTPUT"
echo "skip=${skip}" >> "$GITHUB_OUTPUT"
# Idempotence for re-taps and re-dispatches: a filing that already
# reached a branch is answered with where it is, not filed again (a
# second push to the same branch would not fast-forward anyway, and a
# second branch for the same paper is a merge conflict waiting to
# happen). The sweep treats the comment as "touched" and leaves it.
- name: Refuse a duplicate filing
id: dup
if: steps.resolve.outputs.skip != 'yes'
run: |
skip=no
compare="${GITHUB_SERVER_URL}/${GH_REPO}/compare/main...queue-filing/issue-"
existing=$(git ls-remote --heads origin 'refs/heads/queue-filing/*' \
| sed -n 's#.*refs/heads/queue-filing/issue-\([0-9]*\)$#\1#p')
if grep -qx "$ISSUE" <<<"$existing"; then
gh issue comment "$ISSUE" --body \
"This paper is already filed on branch \`queue-filing/issue-${ISSUE}\` — nothing was filed again. Open its PR with one tap: ${compare}${ISSUE}?expand=1 — merging it closes this issue."
skip=yes
else
title=$(cat /tmp/issue_title.txt)
paper="${title#queue intake: }"; paper="${paper#queue cite: }"
for n in $existing; do
other=$(gh api "repos/${GH_REPO}/issues/${n}" --jq '.title' 2>/dev/null || true)
o_paper="${other#queue intake: }"; o_paper="${o_paper#queue cite: }"
if [ -n "$paper" ] && [ "$paper" = "$o_paper" ]; then
gh issue comment "$ISSUE" --body \
"This paper is already filed on branch \`queue-filing/issue-${n}\` (from #${n}) — nothing was filed again. Open that PR with one tap: ${compare}${n}?expand=1 — then close this issue."
skip=yes
break
fi
done
fi
echo "skip=${skip}" >> "$GITHUB_OUTPUT"
- name: File the paper with Claude
if: steps.resolve.outputs.skip != 'yes' && steps.dup.outputs.skip != 'yes'
uses: anthropics/claude-code-action@v1
with:
# Claude subscription OAuth token — NOT an API key (the org pattern
# from PyAutoMind arxiv_papers.yml / morning_status.yml).
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
# Default-hidden output masks real failures — surface everything.
show_full_output: true
# The dispatch door (queue_actions.yml's hand-over, the nightly
# sweep) runs as github-actions, and the action refuses a run
# "initiated by non-human actor" unless that bot is allowed — the
# first sweep's three filings all died on exactly that line
# (2026-09-10). The `labeled` door is still the human's tap; the
# author-association gate above is what keeps strangers out on
# both doors.
allowed_bots: "github-actions"
# Workspace edits + the repo's own gates + arXiv metadata lookups.
# No git/gh: the deterministic steps below own branch/commit/PR.
claude_args: '--allowedTools "Read,Glob,Grep,Edit,Write,Bash(make:*),Bash(python:*),Bash(python3:*),WebFetch(domain:arxiv.org),WebFetch(domain:export.arxiv.org)"'
prompt: |
You are filing one paper into this PyAutoMemory checkout.
Read /tmp/issue_body.txt (a dashboard queue request). Extract:
- `file:` — which file the line lives in: `reading-queue.md`
(already queued to read), or one of the two overnight suggestion
tiers, NOT yet in the queue — `arxiv-inbox.md` (strong lensing)
and `arxiv-interests.md` (everything else). Absent means
reading-queue.md.
- `section:` — the reading-queue section header,
- `line:` — the exact line as written. A suggestion-tier line is
prefixed `<YYYY-MM-DD> — `, and an arxiv-interests.md one may
carry a further `[Topic] ` after that date. Neither is part of
the title: strip both before using the title anywhere. (The
topic is the same value as `section:`, kept on the line so the
file round-trips on its own.)
- `notes:` — the human's free-text notes; treat as absent if it
still holds the "(optional — replace this…)" placeholder.
Read /tmp/issue_label.txt: `queue-intake` (full filing) or
`queue-cite` (minimal, citeable-only).
Follow this repo's documented workflow — bibliography/README.md
"Adding a paper" and wiki/CLAUDE.md's schema:
1. Identify the paper. Use the arXiv ref if the line has one;
otherwise search arXiv by title (WebFetch against arxiv.org /
export.arxiv.org only). Get authoritative metadata: authors,
year, arXiv id, journal if published.
2. Add the canonical BibTeX entry to the bibliography (the single
canonical .bib file per bibliography/README.md). Follow the
README's key convention exactly.
3. Wiki sources page: pick the sub-wiki whose domain matches the
section (e.g. "Strong Lensing" → wiki/lensing/); check the
sub-wiki index if unsure. In the matching sources/*.md page:
- queue-cite → add a MINIMAL section: heading, the
`**Canonical BibTeX key:**` marker with the new key, and the
human's notes (lightly cleaned up) — nothing deeper.
- queue-intake → add a full stub section per wiki/CLAUDE.md,
folding the notes into the summary.
4. Record it as read, which depends on `file:`:
- `reading-queue.md` → prefix the exact `line:` inside its
`## section` with `DONE <today's date> — ` (never delete it).
- `arxiv-inbox.md` or `arxiv-interests.md` → there is no queue
line yet. Append `DONE <today's date> — <title>[ — <ref>]` to
the end of the `## section` in reading-queue.md (the paper is
being filed now, so it enters the queue already read), then
delete the line from whichever of those two files `file:`
names. A suggestion line is not reading history, so deleting
it is correct — the DONE line it becomes is the history.
- The line may have MOVED since the request was written: an
earlier ➕ tap moves a suggestion into reading-queue.md
unread, and the nightly sweep lapses old inbox lines. So if
`file:` names a suggestion tier and the line is not there,
look for the same paper (by arXiv ref first, then title) in
reading-queue.md: an unread line there is DONE-marked in
place, exactly as the `reading-queue.md` case above. If it
is already DONE-marked anywhere in reading-queue.md, the
paper has been filed before — stop and report that, without
editing anything. If it is in NONE of the three files, the
suggestion simply lapsed before the tap was acted on: the
request itself carries the title and ref, so file it
exactly as the suggestion-tier case above — append the DONE
line to the section — with nothing left to delete.
5. Run `make validate` and `python -m pytest tests/ -q`; fix your
own filing mistakes until both are green.
Touch ONLY bibliography/, wiki/, reading-queue.md,
arxiv-inbox.md and arxiv-interests.md. Do not
commit — the workflow handles git. If the paper cannot be
identified confidently, write the reason to
/tmp/filing_error.txt and stop without editing anything.
- name: Abort cleanly if Claude could not identify the paper
if: steps.resolve.outputs.skip != 'yes' && steps.dup.outputs.skip != 'yes'
run: |
if [ -f /tmp/filing_error.txt ]; then
gh issue comment "$ISSUE" --body-file /tmp/filing_error.txt
echo "::error::filing aborted — reason posted to the issue"
exit 1
fi
- name: Gate the filing (validate + tests)
if: steps.resolve.outputs.skip != 'yes' && steps.dup.outputs.skip != 'yes'
run: |
make validate
python -m pytest tests/ -q
- name: Branch, commit, push, open the PR (explicit paths only)
if: steps.resolve.outputs.skip != 'yes' && steps.dup.outputs.skip != 'yes'
env:
# Empty when the secret is not set; the PR step then falls back to
# the job token and the repo setting decides (header above).
PR_TOKEN: ${{ secrets.QUEUE_PR_TOKEN }}
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
branch="queue-filing/issue-${ISSUE}"
git checkout -b "$branch"
git add bibliography/ wiki/ reading-queue.md arxiv-inbox.md arxiv-interests.md
if git diff --cached --quiet; then
echo "::error::Claude produced no changes"; exit 1
fi
git commit -m "queue: file paper from #${ISSUE}"
# claude-code-action rewires the checkout git credentials for its
# own short-lived OIDC token (the first live run died here with
# 'Invalid username or token') — push with the job token explicitly.
git push "https://x-access-token:${GH_TOKEN}@github.com/${GH_REPO}.git" "HEAD:refs/heads/${branch}"
{
echo "Files the paper requested in #${ISSUE} (label: $(cat /tmp/issue_label.txt))."
echo
echo "Rendered by claude-code-action from the issue's section/line/notes;"
echo "\`make validate\` + \`pytest tests/\` ran green in the filing workflow"
echo "(GITHUB_TOKEN-created PRs do not trigger validate.yml)."
echo
echo "Closes #${ISSUE}."
} > /tmp/pr_body.md
if GH_TOKEN="${PR_TOKEN:-$GH_TOKEN}" gh pr create --base main --head "$branch" \
--title "queue filing: paper from #${ISSUE}" \
--body-file /tmp/pr_body.md; then
gh issue comment "$ISSUE" --body \
"Filing PR opened: $(gh pr view "$branch" --json url --jq .url). Review and merge to complete the intake."
else
# Repo/org policy can forbid Actions-created PRs. The branch is
# pushed and gated — degrade to a one-tap PR link, not a failure.
# The board lists the branch under "Filings awaiting merge" until
# it lands or is deleted.
gh issue comment "$ISSUE" --body \
"Filing branch pushed and gated: \`${branch}\` — but this repo's policy blocks Actions from opening PRs. Open it with one tap: ${GITHUB_SERVER_URL}/${GH_REPO}/compare/main...${branch}?expand=1 — or make this automatic by adding a \`QUEUE_PR_TOKEN\` repo secret (a fine-grained PAT with pull-requests write on this repo; see queue_filing.yml's header). Until it is merged, the paper is on that branch and not in memory; the dashboard lists it under *Filings awaiting merge*."
fi
- name: Report failure on the issue
if: failure()
run: |
gh issue comment "$ISSUE" --body \
"Queue filing failed — see the workflow run: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}. The issue stays open; file manually from a Claude Code session if needed." || true