Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
128 changes: 128 additions & 0 deletions .github/workflows/claude-issue-to-pr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
name: Claude Issue to PR

# Labeling an issue with `claude-fix:<type>` kicks Claude off to
# investigate, make a focused change on a new branch, and open a PR
# linking back to the issue. The label's suffix scopes the ask (typo
# vs docs vs deps vs bug).
#
# Gated to HarperFast org members/collaborators: even though GitHub's
# permission model already restricts who can apply labels, we add an
# explicit author_association check on the issue author so mislabeling
# by an outsider can't trigger work. The action also performs its own
# write-access check on the labeler as a fallback.

on:
issues:
types: [labeled]

concurrency:
group: claude-issue-${{ github.event.issue.number }}
cancel-in-progress: false

jobs:
work:
# Only trigger for `claude-fix:*` labels AND when the issue was
# opened by a HarperFast org member or collaborator. Labels added
# to issues opened by outside contributors are ignored to keep
# the trigger surface tight during calibration.
if: >-
startsWith(github.event.label.name, 'claude-fix:') &&
contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'),
github.event.issue.author_association)
runs-on: ubuntu-latest
timeout-minutes: 25
permissions:
contents: write
pull-requests: write
issues: write
id-token: write

steps:
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
fetch-depth: 0

- name: Setup Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: '22'
cache: 'npm'

- name: Setup Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version: latest

- name: Install dependencies
run: npm ci

- name: Claude (agent mode)
id: claude-agent
uses: anthropics/claude-code-action@c3d45e8e941e1b2ad7b278c57482d9c5bf1f35b3 # v1.0.99
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
show_full_output: true
claude_args: |
--model claude-sonnet-4-6
--max-turns 40
--allowedTools "Read,Write,Edit,Grep,Glob,Bash(gh pr view:*),Bash(gh pr diff:*),Bash(gh pr comment:*),Bash(gh pr create:*),Bash(gh issue view:*),Bash(gh issue comment:*),Bash(git:*),Bash(npm install:*),Bash(npm ci:*),Bash(npm run:*),Bash(npm test:*),Bash(bun install:*),Bash(bun run:*),Bash(bun test:*),Bash(npx:*)"
prompt: |
You were invoked because issue #${{ github.event.issue.number }}
on ${{ github.repository }} was labeled
`${{ github.event.label.name }}`.

## The ask

Title: ${{ github.event.issue.title }}

Body:
${{ github.event.issue.body }}

Source: ${{ github.event.issue.html_url }}

## Label-scoped behavior

The label suffix tells you how much latitude you have:

- `claude-fix:typo` — a single-file typo, prose tweak, or
tiny doc fix. Should be one or two lines changed.
- `claude-fix:docs` — a documentation update. Code should
not be touched unless the doc is literally a code comment.
- `claude-fix:deps` — a dependency version bump. Update
`package.json` and regenerate the lockfile via
`npm install` + verify `npm ci` works.
- `claude-fix:bug` — a focused bug fix with at least one
test that fails before the fix and passes after.

Any ask that requires judgment beyond the label's scope —
new public API, architecture changes, cross-cutting
refactors — is OUT of scope. In that case, comment on the
issue explaining what you see and do NOT open a PR.

## Conventions

Read `CLAUDE.md` in the repo root. Its "Code Conventions"
and "Non-Obvious Gotchas" sections apply. Match the repo's
style.

## Process

1. Create a branch named `claude/fix-${{ github.event.issue.number }}`
(or append `-<short-desc>` if useful).
2. Make the change scoped to the label.
3. Run `npm run build && npm run lint && npm run format:check && npm test`
and `bun test`. Fix anything that fails.
4. Commit with a descriptive message.
5. Push the branch and open a PR via `gh pr create` with a
body that says `Closes #${{ github.event.issue.number }}`.
6. Post a comment on the original issue linking to the PR.

## Must NOT

- Do NOT push to `main` or any `release_*` / `v*.x` branch.
- Do NOT use REQUEST_CHANGES or APPROVE on any PR.
- Do NOT open a PR when the ask is out of scope — comment
and stop.
- Do NOT commit secrets, credentials, or large generated
artifacts (e.g. `node_modules/`, coverage output).
105 changes: 105 additions & 0 deletions .github/workflows/claude-mention.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
name: Claude Mention Handler

# Responds to `@claude …` in PR comments and PR review (inline) comments.
# Claude enters the action's "agent mode": reads the commenter's request,
# uses the PR/issue as context, and can edit + commit + push to the PR
# branch. Gated to HarperFast org members/collaborators so external
# contributors can't trigger work against the repo.

on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]

concurrency:
# Queue explicit mentions per PR/issue — don't cancel. A user who
# mentions Claude twice in quick succession probably wants both
# honored (or the second after the first finishes), not the first
# killed mid-flight.
group: claude-mention-${{ github.event.issue.number || github.event.pull_request.number }}
cancel-in-progress: false

jobs:
work:
# Belt-and-suspenders gate:
# 1. Comment must contain the trigger phrase.
# 2. Commenter must be HarperFast org OWNER / MEMBER or a repo
# COLLABORATOR (the action also performs its own write-access
# check on the actor as a fallback).
if: >-
contains(github.event.comment.body, '@claude') &&
contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'),
github.event.comment.author_association)
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
# Write access is intentional here — mention mode is "do work",
# which means editing files, committing, and pushing (either to
# the PR's branch or a new claude/… branch for issue-originated
# asks).
contents: write
pull-requests: write
issues: write
id-token: write

steps:
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1
with:
fetch-depth: 0

- name: Setup Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: '22'
cache: 'npm'

- name: Setup Bun
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
with:
bun-version: latest

- name: Install dependencies
run: npm ci

- name: Claude (agent mode)
id: claude-agent
uses: anthropics/claude-code-action@c3d45e8e941e1b2ad7b278c57482d9c5bf1f35b3 # v1.0.99
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
show_full_output: true
claude_args: |
--model claude-sonnet-4-6
--max-turns 30
--allowedTools "Read,Write,Edit,Grep,Glob,mcp__github_inline_comment__create_inline_comment,Bash(gh pr view:*),Bash(gh pr diff:*),Bash(gh pr comment:*),Bash(gh pr checkout:*),Bash(gh pr create:*),Bash(gh issue view:*),Bash(gh issue comment:*),Bash(git:*),Bash(npm install:*),Bash(npm ci:*),Bash(npm run:*),Bash(npm test:*),Bash(bun install:*),Bash(bun run:*),Bash(bun test:*),Bash(npx:*)"
# In agent mode the action uses the triggering comment as the
# prompt. The text below layers supplemental context on top.
prompt: |
You were invoked via an `@claude` mention on ${{ github.repository }}.

## Conventions

Read `CLAUDE.md` in the repo root first. Its "Code Conventions"
and "Non-Obvious Gotchas" sections apply to any code you
write. Match the repo's existing style rather than
introducing a new one.

## Before committing

Run `npm run build && npm run lint && npm run format:check && npm test`
in the workspace. Also run `bun test` — Bun is installed in
the runner. Fix anything that fails before committing.

## Output

- Scope your changes to exactly what the mention asked for.
Don't refactor unrelated code.
- Commit with a descriptive message referencing the
issue/PR.
- If the request is ambiguous or you have to make a
judgment call that changes public API or semantics, post
a comment on the PR/issue explaining your interpretation
and stop — do NOT push speculative changes.
- Do NOT use REQUEST_CHANGES or APPROVE on PRs. Post
comments or push commits only.
22 changes: 17 additions & 5 deletions .github/workflows/claude-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,13 @@ jobs:
at start, you can re-invoke if needed)
- `gh pr comment` — post the final review comment

Do NOT write files during the review — not to `.claude-pr/`,
not to `/tmp/`, not anywhere. The `Write` and `Edit` tools
are not allowed. If you want to organize notes, keep them
in-memory and assemble the final PR comment; saving
intermediate drafts to disk wastes turns on permission
denials.

Read `CLAUDE.md` in the repo root first — it has the project
overview, conventions, and (importantly) a "Non-Obvious Gotchas"
section covering Resource API v2 patterns, GenericTrackedObject
Expand Down Expand Up @@ -149,6 +156,10 @@ jobs:
PR_NUMBER: ${{ github.event.pull_request.number }}
PR_URL: ${{ github.event.pull_request.html_url }}
REVIEW_STATUS: ${{ steps.claude-review.outcome }}
# Short repo name for log title/label (e.g. "oauth" in
# "HarperFast/oauth"). Interpolated so this workflow can be
# copied to other HarperFast repos without source edits.
REPO_SHORT: ${{ github.event.repository.name }}
run: |
set -uo pipefail

Expand Down Expand Up @@ -199,18 +210,19 @@ jobs:
# didn't complete cleanly (e.g., max_turns). Findings posted before
# the cutoff are still logged but flagged as potentially incomplete.
if [ "$REVIEW_STATUS" = "success" ]; then
TITLE="[oauth] PR #$PR_NUMBER: $COUNT_PART"
TITLE="[$REPO_SHORT] PR #$PR_NUMBER: $COUNT_PART"
else
TITLE="[oauth] PR #$PR_NUMBER: $COUNT_PART (review $REVIEW_STATUS — may be incomplete)"
TITLE="[$REPO_SHORT] PR #$PR_NUMBER: $COUNT_PART (review $REVIEW_STATUS — may be incomplete)"
fi

BODY=$(printf '**Source:** %s\n**Repo:** oauth\n**PR:** #%s\n**Model:** claude-sonnet-4-6\n**Phase:** baseline\n**Review job status:** %s\n**Date:** %s\n\n---\n\n%s\n' \
"$PR_URL" "$PR_NUMBER" "$REVIEW_STATUS" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$CLAUDE_BODY")
BODY=$(printf '**Source:** %s\n**Repo:** %s\n**PR:** #%s\n**Model:** claude-sonnet-4-6\n**Phase:** baseline\n**Review job status:** %s\n**Date:** %s\n\n---\n\n%s\n' \
"$PR_URL" "$REPO_SHORT" "$PR_NUMBER" "$REVIEW_STATUS" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$CLAUDE_BODY")

PAYLOAD=$(jq -nc \
--arg title "$TITLE" \
--arg repo_label "repo:$REPO_SHORT" \
--arg body "$BODY" \
'{title: $title, body: $body, labels: ["repo:oauth", "verdict:pending", "phase:baseline"]}')
'{title: $title, body: $body, labels: [$repo_label, "verdict:pending", "phase:baseline"]}')

HTTP=$(curl -sS -o /tmp/ai-log-resp.json -w '%{http_code}' -X POST \
-H "Authorization: Bearer $AI_REVIEW_LOG_TOKEN" \
Expand Down
Loading