diff --git a/.github/workflows/claude.yml b/.github/workflows/claude.yml index 6feff6b9..6f310c55 100644 --- a/.github/workflows/claude.yml +++ b/.github/workflows/claude.yml @@ -12,11 +12,23 @@ on: jobs: claude: + # Only run for trusted actors. Fork PRs can modify package.json + # scripts; without this gate, an @claude mention on such a PR + # would execute attacker-controlled code under our secrets and + # write permissions. if: | - (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || - (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || - (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || - (github.event_name == 'issues' && contains(github.event.issue.body, '@claude')) + (github.event_name == 'issue_comment' && + contains(github.event.comment.body, '@claude') && + contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)) || + (github.event_name == 'pull_request_review_comment' && + contains(github.event.comment.body, '@claude') && + contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association)) || + (github.event_name == 'pull_request_review' && + contains(github.event.review.body, '@claude') && + contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.review.author_association)) || + (github.event_name == 'issues' && + contains(github.event.issue.body, '@claude') && + contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.issue.author_association)) runs-on: ubuntu-latest timeout-minutes: 20 permissions: @@ -26,10 +38,20 @@ jobs: id-token: write actions: read # Required for Claude to read CI results on PRs steps: + # Initial checkout so GitHub Actions can resolve the local + # composite action below. The composite re-checks out the + # repo itself; this first checkout only needs the .github + # directory. - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: fetch-depth: 1 + sparse-checkout: .github + + # Checkout + Node + deps so Claude can run build/lint/format + # scripts when asked. + - name: Set up repo and dependencies + uses: ./.github/actions/setup - name: Run Claude Code id: claude @@ -38,3 +60,5 @@ jobs: anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} additional_permissions: | actions: read + claude_args: | + --allowed-tools "Bash(npm run build:*) Bash(npm run prettier:*) Bash(npm run eslint:*)" diff --git a/.github/workflows/upstream-release-docs.yml b/.github/workflows/upstream-release-docs.yml index 0baf5097..535345fb 100644 --- a/.github/workflows/upstream-release-docs.yml +++ b/.github/workflows/upstream-release-docs.yml @@ -558,7 +558,7 @@ jobs: claude_args: | --model claude-opus-4-7 --max-turns 1000 - --allowed-tools "Bash(gh:*)" + --allowed-tools "Bash(gh:*) Bash(npm run build:*) Bash(npm run prettier:*) Bash(npm run eslint:*)" prompt: | You are running in GitHub Actions with no interactive user. Follow these steps exactly and do NOT ask clarifying questions -- proceed @@ -752,7 +752,7 @@ jobs: claude_args: | --model claude-opus-4-7 --max-turns 200 - --allowed-tools "Bash(gh:*)" + --allowed-tools "Bash(gh:*) Bash(npm run build:*) Bash(npm run prettier:*) Bash(npm run eslint:*)" prompt: | You are running in GitHub Actions with no interactive user. Follow these steps exactly and do NOT ask clarifying questions -- proceed diff --git a/AGENTS.md b/AGENTS.md index adc647e8..c8d805e2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -55,9 +55,9 @@ The project uses automated tooling to enforce code quality and formatting standa - **Pre-commit hooks**: lint-staged runs automatically on `git commit`, applying Prettier and appropriate linters to staged files. - **GitHub Actions**: All PRs trigger automated checks (ESLint, Prettier). -- **No manual formatting needed**: The pre-commit hook handles formatting automatically - you do not need to run formatters manually. +- **Don't rely on the pre-commit hook**: lint-staged only fires on `git commit` when Node, dependencies, and husky are all set up. It silently no-ops in CI and unattended contexts (GitHub Actions, scheduled agents) and in local environments where `npm install` hasn't been run. If you edited content, run `npm run prettier:fix` and `npm run eslint:fix` yourself to avoid lint failures on PR CI. -File type to linter mapping (handled automatically by pre-commit hooks): +File type to linter mapping (run manually if the pre-commit hook doesn't fire): - `.md` files: Prettier only - `.mdx` files: Prettier + ESLint