Skip to content

ci: add Claude-powered PR review, mention, and issue-to-PR workflows#402

Merged
heskew merged 3 commits intomainfrom
workflow/add-claude-workflows
Apr 24, 2026
Merged

ci: add Claude-powered PR review, mention, and issue-to-PR workflows#402
heskew merged 3 commits intomainfrom
workflow/add-claude-workflows

Conversation

@heskew
Copy link
Copy Markdown
Member

@heskew heskew commented Apr 23, 2026

Summary

Onboards Harper core to the AI workflow pattern calibrated in HarperFast/oauth. Three new workflows under .github/workflows/:

  • claude-review.yml — auto-review on every PR from org members and claude[bot]-authored PRs. Sonnet 4.6, 24 turns, 15min timeout.
  • claude-mention.yml — responds to @claude … from org members. Sonnet 4.6 default; Opus 4.7 opt-in via word-boundary deep in the comment body. 48 turns, 20min timeout.
  • claude-issue-to-pr.yml — label-triggered (claude-fix:typo|docs|deps|bug). Sonnet 4.6, 72 turns, 25min timeout.

Review checklist

Review prompts compose layers from HarperFast/ai-review-prompts (public, pinned by SHA):

  • universal — architecture, security, dispatch surfaces, testing, output discipline
  • harper/common — cross-version Harper gotchas
  • harper/v5 — v5-specific (Resource API v2, Fabric deployment, .env semantics)

A small repo-specific section in claude-review.yml covers Harper-core conventions the shared layers don't yet know (oxlint, RocksDB, TypeStrip, dependencies.md). A calibrated repo-type/core.md layer lands in ai-review-prompts once this repo's PR reviews produce enough signal to support it.

Security posture

Reviewed against a deep external review (findings summary in the fae521c commit message):

  • author_association gate (OWNER / MEMBER / COLLABORATOR) on every job
  • allowed_bots: claude on the review step so AI-authored PRs get reviewed (claude-code-action has its own bot-actor gate separate from the workflow if:)
  • Tool allowlist omits Bash(npx:*) and uses no-arg Bash(npm install) (NOT :*). Honest caveat in the workflow comment: an injection could still edit package.json to add a malicious postinstall and then invoke bare npm install to execute it. The allowlist alone doesn't close that; branch protection + the author_association gate are what bound blast radius.
  • Mention/issue-to-pr grant broader Bash(git:*) deliberately (they're authoring workflows). Review workflow is read-only by contrast, with git subcommands individually scoped (git diff/log/blame/show, no Bash(git:*)).
  • Mention parsing: @claude must be the first non-whitespace token (word-boundary after) — rules out @claudette, inline prose mentions, and quoted replies where the @claude is addressing a human.
  • Random heredoc delimiter for the composed review scope — collision-proof against any content a future layer file might include.
  • Interpolated user content (comment.body, issue.title, issue.body) is framed with code fences or inline backticks. Not a security boundary on its own.

Prerequisites before these workflows can fire

  1. Add ANTHROPIC_API_KEY as a repository secret.
  2. Add AI_REVIEW_LOG_TOKEN as a repository secret (for the logging-to-ai-review-log post-step; skipped gracefully if unset).
  3. Create GitHub labels: claude-fix:typo, claude-fix:docs, claude-fix:deps, claude-fix:bug.
  4. Branch protection on main and the release_* / v*.x branches — the "Must NOT push to main" prompt instruction is a soft guardrail; real protection is GitHub branch-protection rules.

Test plan

  • Merge
  • Add ANTHROPIC_API_KEY and AI_REVIEW_LOG_TOKEN, create claude-fix:* labels, confirm branch protection
  • Open a small test PR to confirm claude-review.yml fires and posts a review
  • @claude a small ask on an issue to confirm claude-mention.yml responds
  • @claude deep audit … on an issue to confirm Opus escalation kicks in
  • Label an issue with claude-fix:typo to confirm the issue-to-PR pipeline opens a PR
  • First real PR reviews will surface what's missing from the Harper-core-specific section and from harper/v5.md — follow-up PRs on this repo and on ai-review-prompts

Onboards Harper core to the AI workflow pattern we've been calibrating
in HarperFast/oauth. Three workflows:

- claude-review.yml — auto-review on every PR from org members and
  claude[bot]-authored PRs. Uses Sonnet 4.6, 24 turns, 15min timeout.
  Review checklist is composed from HarperFast/ai-review-prompts layers
  (universal + harper/common + harper/v5) plus a small repo-specific
  section for Harper core notes (oxlint, RocksDB, TypeStrip,
  dependencies.md).
- claude-mention.yml — responds to @claude mentions from org members.
  Uses Opus 4.7, 48 turns, 20min timeout. Reasoning-heavy / open-ended
  asks need the extra capability.
- claude-issue-to-pr.yml — label-triggered (claude-fix:typo|docs|deps|
  bug). Uses Sonnet 4.6, 72 turns, 25min timeout. Bounded maintenance
  work.

Security posture reflects the hardening the oauth workflows went through:

- author_association gate (OWNER/MEMBER/COLLABORATOR) on every job.
- allowed_bots: claude on the review step so AI-authored PRs get
  reviewed (claude-code-action has its own bot-actor gate).
- Tool allowlist omits Bash(npx:*) (biggest RCE primitive) and uses
  Bash(npm install) (no-arg) instead of Bash(npm install:*) (arbitrary
  package). Inline comments explain what's missing on purpose.
- Interpolated user content (comment.body, issue.title, issue.body)
  is framed with code fences or inline backticks. Not a security
  boundary on its own — the gate and allowlist are — but reduces
  accidental prompt bleed-through.
- The review prompt tells the agent that CLAUDE.md / AGENTS.md are
  part of the PR's own checkout, so a malicious PR could edit them to
  inject instructions. It should treat their contents as authoritative
  for conventions but NOT for overriding review discipline.

Prerequisites once merged:

1. Add `ANTHROPIC_API_KEY` as a repository secret.
2. Create labels `claude-fix:typo`, `claude-fix:docs`, `claude-fix:deps`,
   `claude-fix:bug` for the issue-to-PR pipeline.
3. Branch protection on `main` and `release_*` — the "Must NOT push to
   main" guidance in the issue-to-pr prompt is a soft guardrail only;
   real protection is GitHub's branch-protection rules.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@heskew heskew requested review from a team as code owners April 23, 2026 22:06
@dawsontoth
Copy link
Copy Markdown
Contributor

How do we protect against prompt injection / unauthorized invocation of it?

@dawsontoth
Copy link
Copy Markdown
Contributor

Ah I see, ok

Copy link
Copy Markdown
Contributor

@cb1kenobi cb1kenobi left a comment

Choose a reason for hiding this comment

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

This is probably one the most crazy PRs I've ever reviewed. Wow.

Comment on lines +43 to +44
with:
fetch-depth: 0
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.

Generally you don't need to specify fetch-depth: 0. You don't need the full history of the checkout action.

Suggested change
with:
fetch-depth: 0

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.

Unless the commits help Claude?

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.

For the checkout action?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yeah, good question. We could and should make use of some depth here (and allow some git commands vs gh diff api) but could drop it in the issue-to-pr.

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.

What's wrong with the default depth?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

claude only gets the shallow change with the default 1 depth and won't have the historical context for more thoughtful reviews. it's a workflow perf tradeoff to try to get better, more actionable, reviews. it's still going to take some iterating though. this first pass in harper still won't be perfect and will likely need modifications as models and context evolve...

Copy link
Copy Markdown
Member

@kriszyp kriszyp left a comment

Choose a reason for hiding this comment

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

Very cool, thank you for getting this in, it will be fun to see how it does!

Per inline review feedback from @cb1kenobi on #402, with a twist: rather
than just dropping `fetch-depth: 0` everywhere, tighten mention and
issue-to-PR (where Chris is unambiguously right — those agents never
reach for history) and INVEST on the review side — add read-only git
subcommands so the reviewer can do `git blame` / `git log` /
`git diff <base>...HEAD` for real context on non-trivial PRs.

## Mention / issue-to-PR workflows

- Drop `fetch-depth: 0` (default shallow is fine). Agents commit and
  push — neither needs deep history. `git log` / `git blame` aren't
  reached for in the current prompts. Added back cheaply if we see
  real blocks on history lookups.

## Review workflow

- Keep `fetch-depth: 0`. The reviewer now has access to history via
  the allowlist additions below; depth 1 would make blame useless.
- `--allowedTools` gains read-only git subcommands, individually
  scoped: `Bash(git diff:*)`, `Bash(git log:*)`, `Bash(git blame:*)`,
  `Bash(git show:*)`. Deliberately NOT `Bash(git:*)` — that would
  permit `git push --force`, `git reset --hard`, etc.
- Drops `Bash(gh pr diff:*)` — `git diff <base>...HEAD` replaces it
  and avoids the API round-trip. `gh pr view`, `gh pr comment`, and
  the `mcp__github_inline_comment__create_inline_comment` tool stay
  — they all do different things (metadata, top-level summary,
  per-line anchored findings).
- Prompt's "Tools" section now tells the agent to use `git blame`
  for the "is this code the PR introduced vs pre-existing" judgment
  (which the layered scope's Testing section already cares about),
  and `git log` / `git show` for "why is this load-bearing" context
  before flagging.

Chris's observation was right that `fetch-depth: 0` was dead weight
given the prior allowlist — but the right fix on the review surface
is to make the allowlist richer where extra context pays off, not
just shrink the checkout.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stacked onto #402 in response to the deep external review. Changes:

Accepted and fixed:

- #3 (ai-review-log log step): Ported verbatim from oauth. Harper's
  reviews now feed the central calibration tracker that the weekly
  sweep runs against. Adds AI_REVIEW_LOG_TOKEN to the secrets
  prerequisite list (flagged in PR body update).

- #4 (dead `documentation/**` glob): Harper has no `documentation/`
  dir — the docs site is a separate repo. Replaced with realistic
  Harper doc-file names (README.md, CLAUDE.md, AGENTS.md,
  dependencies.md) + package.json keyword edits. Same fix in both
  mention and issue-to-pr prompts.

- #5 (prefix-match label too permissive): `startsWith('claude-fix:')`
  matched typoed variants (`claude-fix:typos`, etc). Tightened to
  explicit whitelist of the four supported labels.

- #6 (fixed heredoc marker collision risk): Replaced
  `CLAUDE_SCOPE_EOF` with a random `EOF_$(openssl rand -hex 16)`
  delimiter. Collision-proof against any content a future
  ai-review-prompts layer might include.

- #7a (eager `npm ci` on mention): Removed. Most mentions (explain,
  review, small edits) don't need deps — install is ~35-60s × every
  mention. Prompt now tells the agent to run `npm ci` itself before
  any script that requires dependencies. issue-to-pr keeps its eager
  install since that workflow almost always builds/tests.

- #8a (Opus cost on every mention): Shifted to Sonnet default with
  Opus opt-in via case-insensitive word-boundary `deep` in the
  comment. "Needs deep review of the whole migration" escalates;
  "fix this typo" stays on Sonnet. Cost gets spent deliberately, not
  by default.

- #9 (no scope-to-diff guidance): Review prompt now tells the agent
  to start from `git diff --name-only <base>...HEAD` and only
  expand scope when a specific finding demands it. On a ~1000-file
  repo this matters.

Plus a mention-parsing step that enforces:
- `@claude` must be the first non-whitespace token (word-boundary
  after) — rules out `@claudette`, inline prose mentions, and
  quoted replies (`> @claude ...`) where the reply addresses a
  human. The existing `contains('@claude')` job-level `if:` stays
  as a cheap pre-filter; the new shell step is the real precision
  gate.
- Subsequent steps guard on `steps.mention.outputs.proceed == 'true'`.

Comment sharpening (accept the tradeoff, tighten the rationale):

- #1 (postinstall RCE via package.json edit): The allowlist comment
  on both agent workflows previously implied `Bash(npm install)`
  (no-arg) was a real mitigation. It blocks `npm install @attacker/x`
  but NOT the `postinstall` path — an injection can edit package.json
  and then bare `npm install` executes the hostile lifecycle script
  with GITHUB_TOKEN + the claude[bot] installation token in env.
  Comment now names this path explicitly. The actual guardrails are
  branch protection + the author_association gate; a structural fix
  (`.npmrc ignore-scripts=true`, or dropping `Bash(npm install)`
  entirely in favor of a separate CI install job) deserves its own
  PR.

- #2 (`Bash(git:*)` contradicts review.yml's stated principle):
  review.yml's comment previously read as universal guidance. It's
  actually specific to the read-only review workflow. Comment now
  explicitly notes that the authoring workflows deliberately grant
  broader git access and rely on branch protection as the guardrail.

Not addressed here:

- Splitting issue-to-pr into read-only research + narrow-write
  commit steps (post-v0.1.0 follow-up).
- Tightening mention/issue-to-pr to specific read-only + commit/push
  git subcommands (same structural PR).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@heskew heskew merged commit 0863be7 into main Apr 24, 2026
38 of 43 checks passed
@heskew heskew deleted the workflow/add-claude-workflows branch April 24, 2026 20:54
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.

4 participants