Skip to content

Copier update: claude permissions#113

Merged
ejfine merged 3 commits into
mainfrom
cop-claude-perms
Jun 4, 2026
Merged

Copier update: claude permissions#113
ejfine merged 3 commits into
mainfrom
cop-claude-perms

Conversation

@ejfine

@ejfine ejfine commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Pull in upstream

Also added pytest-mock to dev dependencies in template

Summary by CodeRabbit

  • New Features

    • Added a new skill for efficiently managing and responding to GitHub PR review comments with support for batching code changes and automating reply workflows.
  • Chores

    • Updated development environment: base container image, CI tooling versions (uv, pnpm, copier), pre-commit hooks, and VS Code extensions.
    • Updated Claude Code permissions for improved directory and command access control.
    • Enhanced CI/CD workflows: refined Docker image tagging logic, added artifact retention controls, and improved pre-commit caching.
  • Documentation

    • Refined code guidelines for testing practices and command-line navigation approval rules.

@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR introduces a complete Claude Code skill for addressing GitHub PR review comments via a two-phase workflow (user decision collection followed by batched code-change execution), alongside comprehensive infrastructure updates including devcontainer upgrades, CI/CD workflow enhancements, permission broadening for Claude Code, dependency bumping, and template synchronization across numerous configuration files.

Changes

Claude Code PR Comments Skill

Layer / File(s) Summary
Skill documentation and workflow definition
.claude/skills/address-pr-comments/SKILL.md
Documents the complete end-to-end skill for addressing PR review comments: environment verification with --resume mode, PR identification and validation, Phase 1 user decision collection (code-change queueing, reply-only posting, discuss-first deferral), and Phase 2 batch execution (commit per comment, single push, reply posting after successful push).
Footer validation and GitHub API utilities
.claude/skills/address-pr-comments/check-footer.py, utils.py
check-footer.py detects and appends the AI attribution footer to reply files; utils.py provides owner_repo_from_remote() to extract GitHub owner/repo from git remote via regex on multiple URL formats.
PR comment fetching from GitHub GraphQL and REST
.claude/skills/address-pr-comments/fetch-pr-comments.py
CLI script that queries GitHub GraphQL for resolved review threads, paginates REST endpoints for inline and issue comments, builds threaded structures with reply chains, filters bot comments and user's own comments, and outputs JSON of actionable comment threads.
Reply posting to GitHub via CLI
.claude/skills/address-pr-comments/post-reply.py
Script that posts finalized replies to PR comment threads or issue comments via gh api, handles argument parsing, file I/O, endpoint selection, timeout/error handling, and reply file cleanup.
Template versions of PR comments skill
template/.claude/skills/address-pr-comments/*
Complete copies of SKILL.md, check-footer.py, utils.py, fetch-pr-comments.py, post-reply.py maintained in template/ for use in instantiated repositories.

Infrastructure, Permissions, and Configuration Updates

Layer / File(s) Summary
Claude Code permissions configuration
.claude/settings/permissions/additional-dirs.jsonc, bash.jsonc, write.jsonc
Expanded permissions to allow /tmp and /workspaces directory access, added .claude/skills/**/* execution glob, enumerated git/gh subcommands, added curl http://localhost:1-9 access patterns, broadened pnpm command rules (list/test\*/view), and expanded write paths for workspace tmp directories.
DevContainer base image and feature configuration
.devcontainer/Dockerfile, devcontainer.json, template/ versions
Updated Docker base image from v5.1.5 to v6.0.3-noble, bumped Claude Code feature and VS Code extension versions (Coderabbit, Claude Code, Ruff), wrapped Python-specific Ruff settings in Jinja conditionals, and removed prior Yarn workaround.
Copier template answers and CI tooling versions
.copier-answers.yml, .devcontainer/install-ci-tooling.py
Updated template revision to v0.0.132, disabled TypeScript flag, pinned uv (0.11.17), pnpm (11.5.0), and copier (9.15.1) versions in CI tooling script.
Docker image build workflow with release tagging
.github/reusable_workflows/build-docker-image.yaml
Added optional release-tag and artifact-retention-days inputs; upgraded Docker actions to v4.0.0 and v7.1.0; made git-sha tagging conditional (skip if tag exists); added release-tag validation and immutability checks.
CI validation and workflow infrastructure
.github/workflows/ci.yaml, get-values.yaml, pre-commit.yaml
CI now validates .copier-answers.yml presence post-instantiation, specifies Node.js 24.11.1 for unit tests, and uses pre-commit cache v5.0.5; get-values workflow refactored merge_group PR extraction via env variable.
DevContainer context hashing with uv.lock preprocessing
.github/workflows/hash_git_files.py
Updated file inclusion filter to add devcontainer-lock.json and exclude hash_git_files.py; added get_uv_lock_bytes_for_hashing() to strip root package version lines from uv.lock before checksum computation.
Pre-commit hook action updates
.github/workflows/pre-commit.yaml, template/ version
Updated actions/cache version from v5.0.4 to v5.0.5 for pre-commit hook caching.

Dependency Bumping and Code Quality Rules

Layer / File(s) Summary
Dependency and tool version updates
pyproject.toml, extensions/context.py
Bumped uv (→0.0.40), pnpm, copier (→9.15.1), and ty versions; added uv index configuration with exclude-newer window; updated template context with refreshed backend/frontend/CI/CD tooling versions.
Pre-commit hook revisions
.pre-commit-config.yaml, template/.pre-commit-config.yaml
Updated frozen revisions for typos, xmlformatter, check-jsonschema, and ruff-pre-commit across root and template.
Code quality and testing guidelines
AGENTS.md, template/AGENTS.md
Refined testing rules: conditional logic constraints, single-test invocation examples, magic-number exemptions for 1/0, fixed-message exception patterns with # noqa: PT011, and shell navigation permission rules (auto-approved cd subdir, prompted for cd .. and absolute paths).
Linter configuration
ruff.toml, template/ruff.toml
Updated D413 rule comment to clarify docstring formatting rationale.
Git ignores and build artifact exclusions
.gitignore, template/.gitignore
Added ignores for Claude scheduled task locks, tmp/ directories, test output (screenshots, Playwright reports, test results, Vitest attachments), TypeScript build cache, and Nuxt .nuxtrc.
Copier task executable permissions
copier.yml
Enhanced to run git update-index --chmod=+x for scripts on WSL2/fileMode=false systems, ensuring executable permissions persist in Git index for delete_false_positive_rej.py and address-pr-comments scripts.
Template dependency injection
template/pyproject.toml.jinja
Added pytest-mock to generated project dependencies.

Type Cleanup and Test Infrastructure

Layer / File(s) Summary
Type annotation cleanup
template/src/aws_central_infrastructure/artifact_stores/lib/image_builder.py
Removed inline type:ignore comment from Ec2ImageBuilder RolePolicy role_name assignment.
Test helper centralization
tests/unit/copier_tasks/helpers.py, test_remove_precommit_hooks.py
Created shared run_copier_task helper for script execution; refactored test_remove_precommit_hooks to use centralized helper instead of direct subprocess calls.

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly Related PRs

Suggested Reviewers

  • zendern
  • idonaldson
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is minimal and lacks most required template sections (issue link, detailed explanation of why changes are necessary, side effects, and testing approach). Expand the description to include all template sections: link the relevant issue/thread, explain the necessity of the Copier update, describe side effects, and detail how changes are tested.
Docstring Coverage ⚠️ Warning Docstring coverage is 35.56% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Copier update: claude permissions' directly summarizes the main change in the changeset, which centers on updating Claude Code permissions configurations and related infrastructure.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@ejfine ejfine requested a review from zendern June 4, 2026 13:46
@ejfine ejfine marked this pull request as ready for review June 4, 2026 13:46

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 11

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.claude/skills/address-pr-comments/fetch-pr-comments.py:
- Around line 225-241: The filter that drops threads whose last author is
current_user in _build_pulls_threads removes the replies needed for resume-mode
dedupe; change _build_pulls_threads signature to accept an optional flag
include_latest_by_current_user: bool = False, replace the hard `if
_last_author(t) == current_user: continue` with a conditional that only skips
when include_latest_by_current_user is False, and update any callers (notably
the resume inventory caller) to pass True when they need to retain threads
latest-authored-by-current-user so resume detection can inspect replies; keep
the existing behavior (dropping those threads) when the flag is not set and
still strip out "_html_url" before appending.

In @.claude/skills/address-pr-comments/SKILL.md:
- Around line 69-79: Several markdown fenced-code blocks in the SKILL.md
examples (for instance the block beginning "Resume inventory for PR <n>:" and
the command/prompt examples referencing pr-reply-12345.txt/pr-reply-12346.txt)
are missing explicit language tags and surrounding blank lines; update each
fenced block to use an explicit language label (e.g., ```text, ```bash, or
```json as appropriate) and ensure there is a blank line before and after the
fenced block so it conforms to MD040/MD031. Locate every example/fence in the
document and normalize all triple-backtick fences to include the language and
proper spacing consistently.

In @.github/workflows/ci.yaml:
- Line 182: Replace the mutable tag "actions/cache@v5.0.5" with an immutable
commit SHA for the actions/cache action; locate the uses entry "uses:
actions/cache@v5.0.5" in the workflow and update it to pin the action to its
corresponding full commit hash (the 40‑char SHA) so the workflow references an
immutable revision.

In @.github/workflows/get-values.yaml:
- Around line 52-64: Replace the direct expansion of github.event.number inside
the run block by exporting it as an env var (e.g., PR_NUMBER: ${{
github.event.number }}) alongside MERGE_MSG, then change the run logic to set
full_number from $PR_NUMBER (e.g., [[ '${{ github.event_name }}' =
'pull_request' ]] && full_number="$PR_NUMBER") and keep the existing merge_group
parsing for MERGE_MSG; update references to use full_number and ensure values
are quoted when assigned to avoid word-splitting or injection.

In `@extensions/context.py`:
- Around line 133-134: Update documentation and any template generation logic to
reflect the feature-flag rename and behavior change: replace references to the
old symbol template_uses_javascript with the new template_uses_typescript and
ensure template_uses_python = True is intentionally set; update README /
changelog and devcontainer feature selection logic that consumes
context["template_uses_typescript"] and context["template_uses_python"] so
Python devcontainer features (pytest-mock) are generated correctly and Node
tooling is adjusted, and add a brief note in PR/changelog describing the change.

In `@pyproject.toml`:
- Around line 21-24: The current PyPI index configuration under
[[tool.uv.index]] uses exclude-newer = "1 day", which is likely too short and
can exclude recently published dependencies causing build failures; update the
exclude-newer value for the index with name = "pypi" from "1 day" to a longer
window (e.g., "7 days") to improve stability, and ensure any config comments or
repository template documentation reflecting tool.uv.index are updated to
explain the chosen window so consumers know why it's longer.

In `@template/.claude/settings/permissions/bash.jsonc`:
- Line 117: The current permission "Bash(pnpm test*)" is too broad; restrict it
to explicit allowed test scripts by replacing the wildcard with concrete script
names (e.g., "Bash(pnpm test-unit)" and any other approved scripts like
"Bash(pnpm test-integration)" if needed). Edit the permissions entry that
currently references Bash(pnpm test*) to list only the exact pnpm test script
names your project uses (match package.json script keys) so only those
known-safe commands can run. Ensure you update any related documentation or
comments to note the approved script names and avoid allowing direct invocations
like "pnpm vitest" or "npx vitest".

In `@template/.claude/skills/address-pr-comments/fetch-pr-comments.py`:
- Line 84: The inline noqa directive on the function signature gh_graphql(query:
str, variables: dict[str, str | int]) -> Any currently disables ANN401 without
explanation; update that line to keep the noqa but append a brief comment
explaining the rationale (e.g., why return type Any is acceptable or why precise
annotation isn’t feasible here) so the noqa has a justification, referencing the
gh_graphql function and ANN401 rule in the comment.
- Around line 90-91: The inline "noqa: S603" on the subprocess.run call lacks an
explanation; update the subprocess.run invocation (the line with "result =
subprocess.run(... args,") to retain the noqa but add a short comment explaining
why rule S603 is being suppressed (e.g., safe use of subprocess with validated
args or controlled input), mentioning the rationale and any mitigation (input
validation/escaping) so reviewers can see why subprocess.run is acceptable here.

In `@template/.github/workflows/get-values.yaml`:
- Around line 52-64: The workflow currently expands ${{ github.event.number }}
directly inside the run block; add a new environment variable (e.g., PR_NUMBER)
at the same env level as MERGE_MSG and set it to ${{ github.event.number }},
then update the run script to reference $PR_NUMBER instead of expanding
github.event.number inline (adjust the conditional that sets full_number from
full_number=${{ github.event.number }} to full_number=$PR_NUMBER and keep the
existing merge_group extraction logic using $MERGE_MSG and $full_number). Ensure
the conditional comparison of '${{ github.event_name }}' remains unchanged but
any occurrence of inline github.event.* values in the run block is replaced with
corresponding env vars like PR_NUMBER for consistency and safer escaping.

In `@tests/unit/copier_tasks/helpers.py`:
- Around line 6-10: Change run_copier_task so its multi-argument API is
keyword-only: update the function signature (run_copier_task) to force callers
to pass script_path, args (or a single variadic collection param) and env by
name (use a leading * in the signature) and keep the return type
subprocess.CompletedProcess[str]; then update all callers to call
run_copier_task(script_path=..., args=..., env=...) (or script_path=... and
positional args moved into an explicit args parameter) to match the new
signature and types.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: b5deabbb-003b-41bd-b435-611904979bdf

📥 Commits

Reviewing files that changed from the base of the PR and between a6a7a5c and 6c60677.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (45)
  • .claude/settings/permissions/additional-dirs.jsonc
  • .claude/settings/permissions/bash.jsonc
  • .claude/settings/permissions/write.jsonc
  • .claude/skills/address-pr-comments/SKILL.md
  • .claude/skills/address-pr-comments/check-footer.py
  • .claude/skills/address-pr-comments/fetch-pr-comments.py
  • .claude/skills/address-pr-comments/post-reply.py
  • .claude/skills/address-pr-comments/utils.py
  • .copier-answers.yml
  • .devcontainer/Dockerfile
  • .devcontainer/devcontainer.json
  • .devcontainer/install-ci-tooling.py
  • .github/reusable_workflows/build-docker-image.yaml
  • .github/workflows/ci.yaml
  • .github/workflows/get-values.yaml
  • .github/workflows/hash_git_files.py
  • .github/workflows/pre-commit.yaml
  • .gitignore
  • .pre-commit-config.yaml
  • AGENTS.md
  • copier.yml
  • extensions/context.py
  • pyproject.toml
  • ruff.toml
  • template/.claude/settings/permissions/additional-dirs.jsonc
  • template/.claude/settings/permissions/bash.jsonc
  • template/.claude/settings/permissions/write.jsonc
  • template/.claude/skills/address-pr-comments/SKILL.md
  • template/.claude/skills/address-pr-comments/check-footer.py
  • template/.claude/skills/address-pr-comments/fetch-pr-comments.py
  • template/.claude/skills/address-pr-comments/post-reply.py
  • template/.claude/skills/address-pr-comments/utils.py
  • template/.devcontainer/Dockerfile
  • template/.devcontainer/devcontainer.json.jinja
  • template/.github/workflows/get-values.yaml
  • template/.github/workflows/hash_git_files.py
  • template/.github/workflows/pre-commit.yaml
  • template/.gitignore
  • template/.pre-commit-config.yaml
  • template/AGENTS.md
  • template/pyproject.toml.jinja
  • template/ruff.toml
  • template/src/aws_central_infrastructure/artifact_stores/lib/image_builder.py
  • tests/unit/copier_tasks/helpers.py
  • tests/unit/copier_tasks/test_remove_precommit_hooks.py

Comment on lines +225 to +241
def _build_pulls_threads(
pulls_raw: list[dict[str, Any]],
resolved_urls: set[str],
current_user: str,
) -> list[dict[str, Any]]:
"""Group inline review comments into threads, filtering resolved + already-addressed."""
roots = _collect_thread_roots(pulls_raw)
_attach_replies(pulls_raw, roots)

threads: list[dict[str, Any]] = []
for t in roots.values():
if t["_html_url"] in resolved_urls:
continue
if _last_author(t) == current_user:
continue
threads.append({k: v for k, v in t.items() if k != "_html_url"})
return threads

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Resume-mode dedupe signal is dropped by the current-user-last filter.

On Line 238, threads whose latest reply is by current_user are removed, but resume mode in .claude/skills/address-pr-comments/SKILL.md relies on thread replies to detect “already manually replied, do not repost.” This makes that invariant unreachable for those threads and can misclassify resume artifacts.

A robust fix is to make this filter optional (e.g., an include_latest_by_current_user mode for resume inventory) and enable that mode when Step 1 resume inventory calls the fetch script.

Suggested direction
-def _build_pulls_threads(
+def _build_pulls_threads(
     pulls_raw: list[dict[str, Any]],
     resolved_urls: set[str],
     current_user: str,
+    *,
+    include_latest_by_current_user: bool,
 ) -> list[dict[str, Any]]:
@@
-        if _last_author(t) == current_user:
+        if not include_latest_by_current_user and _last_author(t) == current_user:
             continue
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.claude/skills/address-pr-comments/fetch-pr-comments.py around lines 225 -
241, The filter that drops threads whose last author is current_user in
_build_pulls_threads removes the replies needed for resume-mode dedupe; change
_build_pulls_threads signature to accept an optional flag
include_latest_by_current_user: bool = False, replace the hard `if
_last_author(t) == current_user: continue` with a conditional that only skips
when include_latest_by_current_user is False, and update any callers (notably
the resume inventory caller) to pass True when they need to retain threads
latest-authored-by-current-user so resume detection can inspect replies; keep
the existing behavior (dropping those threads) when the flag is not set and
still strip out "_html_url" before appending.

Comment on lines +69 to +79
```
Resume inventory for PR <n>:
- <X> unpushed commits: <hash list>
- <Y> reply drafts:
- pr-reply-12345.txt — has [COMMIT LINK] placeholder, needs commit
- pr-reply-12346.txt — finalized, paired with commit abc1234
- pr-reply-12347.txt — orphan (comment 12347 no longer unresolved)
- <Z> in_progress bd issues: bd-87, bd-88

How to proceed?
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix markdown fence lint violations in command/prompt examples.

Several fenced blocks are missing explicit languages and/or surrounding blank lines, which matches the current markdownlint findings (MD040/MD031). Please normalize those blocks so docs lint stays clean.

Also applies to: 139-149, 163-175, 224-238

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 69-69: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.claude/skills/address-pr-comments/SKILL.md around lines 69 - 79, Several
markdown fenced-code blocks in the SKILL.md examples (for instance the block
beginning "Resume inventory for PR <n>:" and the command/prompt examples
referencing pr-reply-12345.txt/pr-reply-12346.txt) are missing explicit language
tags and surrounding blank lines; update each fenced block to use an explicit
language label (e.g., ```text, ```bash, or ```json as appropriate) and ensure
there is a blank line before and after the fenced block so it conforms to
MD040/MD031. Locate every example/fence in the document and normalize all
triple-backtick fences to include the language and proper spacing consistently.

Comment thread .github/workflows/ci.yaml

- name: Cache Pre-commit hooks
uses: actions/cache@v5.0.4
uses: actions/cache@v5.0.5

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Pin actions/cache to an immutable commit SHA.

Line 182 uses a mutable tag (v5.0.5), which weakens workflow supply-chain guarantees.

Suggested change
-        uses: actions/cache@v5.0.5
+        uses: actions/cache@<40-char-commit-sha> # v5.0.5

Use this read-only check to find unpinned uses entries in this workflow before/after the fix:

#!/bin/bash
rg -n '^\s*uses:\s*[^@]+@(v[0-9]|[0-9]+\.[0-9]+)' .github/workflows/ci.yaml
rg -n '^\s*uses:\s*[^@]+@[0-9a-f]{40}\b' .github/workflows/ci.yaml
🧰 Tools
🪛 zizmor (1.25.2)

[error] 182-182: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yaml at line 182, Replace the mutable tag
"actions/cache@v5.0.5" with an immutable commit SHA for the actions/cache
action; locate the uses entry "uses: actions/cache@v5.0.5" in the workflow and
update it to pin the action to its corresponding full commit hash (the 40‑char
SHA) so the workflow references an immutable revision.

Comment on lines +52 to +64
env:
MERGE_MSG: ${{ github.event.merge_group.head_commit.message }}
run: |

[[ '${{ github.event_name }}' = 'pull_request' ]] && full_number=${{ github.event.number }}

# example message in merge group context: "Merge pull request #10 from org-name/branch-name\n\ncommit message"
[[ '${{ github.event_name }}' = 'merge_group' ]] && message='${{ github.event.merge_group.head_commit.message }}' && echo Extracting from $message && number_and_following_text=${message##*#} && full_number=${number_and_following_text%%[!0-9]*}
if [[ '${{ github.event_name }}' = 'merge_group' ]]; then
message="$MERGE_MSG"
echo "Extracting from $message"
number_and_following_text="${message##*#}"
full_number="${number_and_following_text%%[!0-9]*}"
fi

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Consider moving all GitHub context values to environment variables for consistency.

The refactoring to use MERGE_MSG env variable for the merge commit message (lines 52-53) is a security improvement because GitHub Actions automatically escapes env variables, preventing template injection. However, line 56 still directly expands ${{ github.event.number }} in the run block.

For defense-in-depth and consistency, consider moving github.event.number to an env variable as well:

🔒 Proposed hardening
 env:
   MERGE_MSG: ${{ github.event.merge_group.head_commit.message }}
+  PR_NUMBER: ${{ github.event.number }}
 run: |

-  [[ '${{ github.event_name }}' = 'pull_request' ]] && full_number=${{ github.event.number }}
+  [[ '${{ github.event_name }}' = 'pull_request' ]] && full_number="$PR_NUMBER"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
env:
MERGE_MSG: ${{ github.event.merge_group.head_commit.message }}
run: |
[[ '${{ github.event_name }}' = 'pull_request' ]] && full_number=${{ github.event.number }}
# example message in merge group context: "Merge pull request #10 from org-name/branch-name\n\ncommit message"
[[ '${{ github.event_name }}' = 'merge_group' ]] && message='${{ github.event.merge_group.head_commit.message }}' && echo Extracting from $message && number_and_following_text=${message##*#} && full_number=${number_and_following_text%%[!0-9]*}
if [[ '${{ github.event_name }}' = 'merge_group' ]]; then
message="$MERGE_MSG"
echo "Extracting from $message"
number_and_following_text="${message##*#}"
full_number="${number_and_following_text%%[!0-9]*}"
fi
env:
MERGE_MSG: ${{ github.event.merge_group.head_commit.message }}
PR_NUMBER: ${{ github.event.number }}
run: |
[[ '${{ github.event_name }}' = 'pull_request' ]] && full_number="$PR_NUMBER"
# example message in merge group context: "Merge pull request `#10` from org-name/branch-name\n\ncommit message"
if [[ '${{ github.event_name }}' = 'merge_group' ]]; then
message="$MERGE_MSG"
echo "Extracting from $message"
number_and_following_text="${message##*#}"
full_number="${number_and_following_text%%[!0-9]*}"
fi
🧰 Tools
🪛 zizmor (1.25.2)

[warning] 56-56: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)


[warning] 56-56: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)


[warning] 59-59: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/get-values.yaml around lines 52 - 64, Replace the direct
expansion of github.event.number inside the run block by exporting it as an env
var (e.g., PR_NUMBER: ${{ github.event.number }}) alongside MERGE_MSG, then
change the run logic to set full_number from $PR_NUMBER (e.g., [[ '${{
github.event_name }}' = 'pull_request' ]] && full_number="$PR_NUMBER") and keep
the existing merge_group parsing for MERGE_MSG; update references to use
full_number and ensure values are quoted when assigned to avoid word-splitting
or injection.

Comment thread extensions/context.py
Comment on lines +133 to +134
context["template_uses_typescript"] = False
context["template_uses_python"] = True

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Document the template feature flag change.

The PR description mentions adding pytest-mock but doesn't document this behavioral change:

  • template_uses_javascript was replaced with template_uses_typescript
  • template_uses_python is now set to True

This affects which devcontainer features are generated (Python vs Node tooling). Verify this change was intentional and aligns with the template's evolution.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@extensions/context.py` around lines 133 - 134, Update documentation and any
template generation logic to reflect the feature-flag rename and behavior
change: replace references to the old symbol template_uses_javascript with the
new template_uses_typescript and ensure template_uses_python = True is
intentionally set; update README / changelog and devcontainer feature selection
logic that consumes context["template_uses_typescript"] and
context["template_uses_python"] so Python devcontainer features (pytest-mock)
are generated correctly and Node tooling is adjusted, and add a brief note in
PR/changelog describing the change.

"Bash(pnpm test-unit *)",
"Bash(pnpm test-e2e *)",
"Bash(pnpm list *)",
"Bash(pnpm test*)",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Narrow the pnpm allow-list to explicit test scripts.

Line [117] ("Bash(pnpm test*)" ) allows any test* script name, which can execute unintended high-impact commands defined in package.json. Keep this permission explicit to known-safe scripts.

Suggested change
-      "Bash(pnpm test*)",
+      "Bash(pnpm test-unit *)",
+      "Bash(pnpm test-e2e *)",

Based on learnings: “For frontend tests, run commands via pnpm scripts … Always use pnpm test-unit, never pnpm vitest ... or npx vitest ...”.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@template/.claude/settings/permissions/bash.jsonc` at line 117, The current
permission "Bash(pnpm test*)" is too broad; restrict it to explicit allowed test
scripts by replacing the wildcard with concrete script names (e.g., "Bash(pnpm
test-unit)" and any other approved scripts like "Bash(pnpm test-integration)" if
needed). Edit the permissions entry that currently references Bash(pnpm test*)
to list only the exact pnpm test script names your project uses (match
package.json script keys) so only those known-safe commands can run. Ensure you
update any related documentation or comments to note the approved script names
and avoid allowing direct invocations like "pnpm vitest" or "npx vitest".

return [item for page in data for item in page]


def gh_graphql(query: str, variables: dict[str, str | int]) -> Any: # noqa: ANN401

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add explanation to noqa comment.

The noqa: ANN401 directive is missing an explanation. As per coding guidelines, when disabling a linting rule, provide a comment describing the reasoning.

📝 Suggested fix
-def gh_graphql(query: str, variables: dict[str, str | int]) -> Any:  # noqa: ANN401
+def gh_graphql(query: str, variables: dict[str, str | int]) -> Any:  # noqa: ANN401 — return type is genuinely unknown JSON

As per coding guidelines, when disabling a linting rule with an inline directive, provide a comment describing the reasoning for disabling the rule.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def gh_graphql(query: str, variables: dict[str, str | int]) -> Any: # noqa: ANN401
def gh_graphql(query: str, variables: dict[str, str | int]) -> Any: # noqa: ANN401 — return type is genuinely unknown JSON
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@template/.claude/skills/address-pr-comments/fetch-pr-comments.py` at line 84,
The inline noqa directive on the function signature gh_graphql(query: str,
variables: dict[str, str | int]) -> Any currently disables ANN401 without
explanation; update that line to keep the noqa but append a brief comment
explaining the rationale (e.g., why return type Any is acceptable or why precise
annotation isn’t feasible here) so the noqa has a justification, referencing the
gh_graphql function and ANN401 rule in the comment.

Comment on lines +90 to +91
result = subprocess.run( # noqa: S603
args,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add explanation to noqa comment.

The noqa: S603 directive is missing an explanation. As per coding guidelines, when disabling a linting rule, provide a comment describing the reasoning.

📝 Suggested fix
-    result = subprocess.run(  # noqa: S603
+    result = subprocess.run(  # noqa: S603 — args constructed from validated inputs

As per coding guidelines, when disabling a linting rule with an inline directive, provide a comment describing the reasoning for disabling the rule.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
result = subprocess.run( # noqa: S603
args,
result = subprocess.run( # noqa: S603 — args constructed from validated inputs
args,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@template/.claude/skills/address-pr-comments/fetch-pr-comments.py` around
lines 90 - 91, The inline "noqa: S603" on the subprocess.run call lacks an
explanation; update the subprocess.run invocation (the line with "result =
subprocess.run(... args,") to retain the noqa but add a short comment explaining
why rule S603 is being suppressed (e.g., safe use of subprocess with validated
args or controlled input), mentioning the rationale and any mitigation (input
validation/escaping) so reviewers can see why subprocess.run is acceptable here.

Comment on lines +52 to +64
env:
MERGE_MSG: ${{ github.event.merge_group.head_commit.message }}
run: |

[[ '${{ github.event_name }}' = 'pull_request' ]] && full_number=${{ github.event.number }}

# example message in merge group context: "Merge pull request #10 from org-name/branch-name\n\ncommit message"
[[ '${{ github.event_name }}' = 'merge_group' ]] && message='${{ github.event.merge_group.head_commit.message }}' && echo Extracting from $message && number_and_following_text=${message##*#} && full_number=${number_and_following_text%%[!0-9]*}
if [[ '${{ github.event_name }}' = 'merge_group' ]]; then
message="$MERGE_MSG"
echo "Extracting from $message"
number_and_following_text="${message##*#}"
full_number="${number_and_following_text%%[!0-9]*}"
fi

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Consider moving all GitHub context values to environment variables for consistency.

The refactoring to use MERGE_MSG env variable for the merge commit message (lines 52-53) is a security improvement because GitHub Actions automatically escapes env variables, preventing template injection. However, line 56 still directly expands ${{ github.event.number }} in the run block.

For defense-in-depth and consistency, consider moving github.event.number to an env variable as well:

🔒 Proposed hardening
 env:
   MERGE_MSG: ${{ github.event.merge_group.head_commit.message }}
+  PR_NUMBER: ${{ github.event.number }}
 run: |

-  [[ '${{ github.event_name }}' = 'pull_request' ]] && full_number=${{ github.event.number }}
+  [[ '${{ github.event_name }}' = 'pull_request' ]] && full_number="$PR_NUMBER"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
env:
MERGE_MSG: ${{ github.event.merge_group.head_commit.message }}
run: |
[[ '${{ github.event_name }}' = 'pull_request' ]] && full_number=${{ github.event.number }}
# example message in merge group context: "Merge pull request #10 from org-name/branch-name\n\ncommit message"
[[ '${{ github.event_name }}' = 'merge_group' ]] && message='${{ github.event.merge_group.head_commit.message }}' && echo Extracting from $message && number_and_following_text=${message##*#} && full_number=${number_and_following_text%%[!0-9]*}
if [[ '${{ github.event_name }}' = 'merge_group' ]]; then
message="$MERGE_MSG"
echo "Extracting from $message"
number_and_following_text="${message##*#}"
full_number="${number_and_following_text%%[!0-9]*}"
fi
env:
MERGE_MSG: ${{ github.event.merge_group.head_commit.message }}
PR_NUMBER: ${{ github.event.number }}
run: |
[[ '${{ github.event_name }}' = 'pull_request' ]] && full_number="$PR_NUMBER"
# example message in merge group context: "Merge pull request `#10` from org-name/branch-name\n\ncommit message"
if [[ '${{ github.event_name }}' = 'merge_group' ]]; then
message="$MERGE_MSG"
echo "Extracting from $message"
number_and_following_text="${message##*#}"
full_number="${number_and_following_text%%[!0-9]*}"
fi
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@template/.github/workflows/get-values.yaml` around lines 52 - 64, The
workflow currently expands ${{ github.event.number }} directly inside the run
block; add a new environment variable (e.g., PR_NUMBER) at the same env level as
MERGE_MSG and set it to ${{ github.event.number }}, then update the run script
to reference $PR_NUMBER instead of expanding github.event.number inline (adjust
the conditional that sets full_number from full_number=${{ github.event.number
}} to full_number=$PR_NUMBER and keep the existing merge_group extraction logic
using $MERGE_MSG and $full_number). Ensure the conditional comparison of '${{
github.event_name }}' remains unchanged but any occurrence of inline
github.event.* values in the run block is replaced with corresponding env vars
like PR_NUMBER for consistency and safer escaping.

Comment on lines +6 to +10
def run_copier_task(
script_path: Path,
*args: str,
env: dict[str, str] | None = None,
) -> subprocess.CompletedProcess[str]:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Make run_copier_task keyword-only to match project convention.

Line 6 exposes positional parameters for a multi-argument helper. Convert this API to keyword-only and update callers for explicitness and consistency.

Proposed refactor
diff --git a/tests/unit/copier_tasks/helpers.py b/tests/unit/copier_tasks/helpers.py
@@
-def run_copier_task(
-    script_path: Path,
-    *args: str,
-    env: dict[str, str] | None = None,
-) -> subprocess.CompletedProcess[str]:
+def run_copier_task(
+    *,
+    script_path: Path,
+    args: tuple[str, ...] = (),
+    env: dict[str, str] | None = None,
+) -> subprocess.CompletedProcess[str]:
     return subprocess.run(  # noqa: S603 -- these are our own scripts
-        [sys.executable, str(script_path), *args],
+        [sys.executable, str(script_path), *args],
         check=False,
         capture_output=True,
         text=True,
         env=env,
     )
diff --git a/tests/unit/copier_tasks/test_remove_precommit_hooks.py b/tests/unit/copier_tasks/test_remove_precommit_hooks.py
@@
-        return run_copier_task(_SCRIPT_PATH, "--hook-id-regex", hook_id_regex, "--target-file", str(target_file))
+        return run_copier_task(
+            script_path=_SCRIPT_PATH,
+            args=("--hook-id-regex", hook_id_regex, "--target-file", str(target_file)),
+        )

As per coding guidelines, "**/*.{py,ts,tsx}: Prefer keyword-only parameters (unless a very clear single-argument function): use * in Python signatures."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/unit/copier_tasks/helpers.py` around lines 6 - 10, Change
run_copier_task so its multi-argument API is keyword-only: update the function
signature (run_copier_task) to force callers to pass script_path, args (or a
single variadic collection param) and env by name (use a leading * in the
signature) and keep the return type subprocess.CompletedProcess[str]; then
update all callers to call run_copier_task(script_path=..., args=..., env=...)
(or script_path=... and positional args moved into an explicit args parameter)
to match the new signature and types.

@ejfine ejfine merged commit 85bb814 into main Jun 4, 2026
19 checks passed
@ejfine ejfine deleted the cop-claude-perms branch June 4, 2026 14:03
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.

2 participants