Skip to content

feat: protect any top-level folder starting with '.' in safe outputs#28486

Merged
pelikhan merged 3 commits intomainfrom
copilot/add-top-level-folder-protection
Apr 25, 2026
Merged

feat: protect any top-level folder starting with '.' in safe outputs#28486
pelikhan merged 3 commits intomainfrom
copilot/add-top-level-folder-protection

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 25, 2026

Summary

Adds a general protection rule that blocks AI agents from modifying any top-level repository directory whose name starts with . (e.g. .cursor/, .vscode/, .devcontainer/) via the create-pull-request and push-to-pull-request-branch safe output handlers.

Previously only a hardcoded list (.github/, .agents/, .githooks/, .husky/) was protected. The new rule covers any present or future dot-directory at the repository root without requiring an explicit list update.

Changes

Go (compile-time)

  • pkg/workflow/runtime_definitions.go: Add getDotFolderExcludes() helper that extracts dot-folder path prefixes (e.g. .cursor/) from an exclusion list
  • pkg/workflow/compiler_safe_outputs_handlers.go: Add protect_top_level_dot_folders: true to both create_pull_request and push_to_pull_request_branch handler configs
  • pkg/workflow/compiler_safe_outputs_config.go + safe_outputs_config_generation.go: Compute and pass protected_dot_folder_excludes so the runtime handler respects per-folder exclusions from the exclude: list

JavaScript (runtime)

  • actions/setup/js/manifest_file_helpers.cjs: Add checkForTopLevelDotFolders() and update checkFileProtection() to apply the new rule with deduplication
  • actions/setup/js/types/handler-factory.d.ts: Add protect_top_level_dot_folders and protected_dot_folder_excludes to HandlerConfig

Tests

  • Go: TestGetDotFolderExcludes, TestProtectTopLevelDotFolders, TestProtectedDotFolderExcludes, TestNoProtectedDotFolderExcludesWhenNoneDotFolderExcluded
  • JS: checkForTopLevelDotFolders describe block + new checkFileProtection dot-folder test cases (62 tests pass)

Docs

  • Updated safe-outputs-pull-requests.md with a "What Is Protected" section and clarified how the exclude: list interacts with the new dot-folder rule

Exclusion behaviour

Users can still opt specific dot-folders out of protection via the existing exclude: list:

safe-outputs:
  create-pull-request:
    protected-files:
      policy: fallback-to-issue
      exclude:
        - .cursor/   # allow agent to update .cursor/ settings

Dot-folder entries in the exclude: list are forwarded to the runtime as protected_dot_folder_excludes so the general rule skips them correctly.

- Add getDotFolderExcludes() helper in runtime_definitions.go to identify
  dot-folder path prefixes from exclusion lists
- Add protect_top_level_dot_folders: true to create_pull_request and
  push_to_pull_request_branch handler configs
- Compute and pass protected_dot_folder_excludes to the runtime handler
  so excluded dot-folders are respected by the new rule
- Add checkForTopLevelDotFolders() in manifest_file_helpers.cjs to flag
  any file under a root-level dot-directory (e.g. .cursor/, .vscode/)
- Update checkFileProtection() to use the new check with deduplication
- Update TypeScript definitions in handler-factory.d.ts
- Add tests for all new functionality (JS and Go)
- Update protected-files documentation

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/21084b2d-446c-4182-b075-0142ef23de42

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI requested a review from pelikhan April 25, 2026 17:59
@pelikhan pelikhan marked this pull request as ready for review April 25, 2026 18:28
Copilot AI review requested due to automatic review settings April 25, 2026 18:28
@github-actions github-actions Bot mentioned this pull request Apr 25, 2026
@pelikhan
Copy link
Copy Markdown
Collaborator

@copilot remove the protected paths that start with '.' from the computed list since they are protected by default

@github-actions
Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 85/100

Excellent test quality

Metric Value
New/modified tests analyzed 12
✅ Design tests (behavioral contracts) 12 (100%)
⚠️ Implementation tests (low value) 0 (0%)
Tests with error/edge cases 10 (83%)
Duplicate test clusters 0
Test inflation detected Yes (see below)
🚨 Coding-guideline violations None

Test Classification Details

View All Test Classifications (12 tests)
Test File Classification Notes
TestGetDotFolderExcludes (6 table rows) pkg/workflow/compiler_safe_outputs_config_test.go ✅ Design Table-driven; covers nil, no dot-folders, single prefix, mixed, dot-file-only, ./ edge cases
TestProtectTopLevelDotFolders pkg/workflow/compiler_safe_outputs_config_test.go ✅ Design Verifies compiled JSON config has protect_top_level_dot_folders: true for both handlers
TestProtectedDotFolderExcludes pkg/workflow/compiler_safe_outputs_config_test.go ✅ Design Verifies dot-folder excludes propagate; sentinel key absent; non-dot-folder files excluded
TestNoProtectedDotFolderExcludesWhenNoneDotFolderExcluded pkg/workflow/compiler_safe_outputs_config_test.go ✅ Design Verifies key is absent when no dot-folder excludes present
should return false for empty patch actions/setup/js/manifest_file_helpers.test.cjs ✅ Design Empty/nil edge case
should detect a file inside a top-level dot-folder actions/setup/js/manifest_file_helpers.test.cjs ✅ Design Core behavioral contract
should detect multiple dot-folders actions/setup/js/manifest_file_helpers.test.cjs ✅ Design Multi-item behavior
should not flag root-level dot-files (not inside a dot-folder) actions/setup/js/manifest_file_helpers.test.cjs ✅ Design Key edge case — .env vs .cursor/ distinction
should not flag files inside non-dot top-level folders actions/setup/js/manifest_file_helpers.test.cjs ✅ Design Boundary: src/index.js not flagged
should not flag files already covered by .github/ when that is in excludes actions/setup/js/manifest_file_helpers.test.cjs ✅ Design Exclusion filter behavioral contract
should still flag non-excluded dot-folders when some are excluded actions/setup/js/manifest_file_helpers.test.cjs ✅ Design Mixed exclusion contract
should not flag deeply nested files under non-dot top-level folders actions/setup/js/manifest_file_helpers.test.cjs ✅ Design Depth boundary — pkg/workflow/.hidden/ not flagged

Flagged Tests — Requires Review

i️ Test Inflation — compiler_safe_outputs_config_test.go

Classification: Informational only (does not affect PASS/FAIL)
Observation: +190 lines added to the test file vs +5 lines in the corresponding production file compiler_safe_outputs_config.go. Ratio ≈ 38:1.
Context: The feature spans multiple production files (compiler_safe_outputs_config.go +5, compiler_safe_outputs_handlers.go +2, safe_outputs_config_generation.go +5, runtime_definitions.go +16 = ~28 total), bringing the effective ratio to ~7:1 — still above 2:1 by the rubric. However, all 4 test functions verify meaningful end-to-end behavioral contracts through the compiled JSON output, so this is legitimate integration-style test coverage for a cross-cutting feature, not test padding.

i️ TestProtectTopLevelDotFolders — No Error Path

Classification: Minor gap (informational)
Observation: Tests only the happy path (both handlers present with protect_top_level_dot_folders: true). No test for a workflow that lacks CreatePullRequests or PushToPullRequestBranch config entirely.
Suggested improvement: Add a table row or companion test verifying that a workflow without safe-outputs config does not emit the key (to guard against spurious defaults).


Language Support

Tests analyzed:

  • 🐹 Go (*_test.go): 4 test functions (unit, //go:build !integration) ✓
  • 🟨 JavaScript (*.test.cjs): 8 it() blocks (vitest) ✓

Verdict

Check passed. 0% of new tests are implementation tests (threshold: 30%). All 12 tests verify observable behavioral contracts with strong edge-case coverage. No coding-guideline violations detected (build tag present, no mock libraries used, assertion messages present throughout).


📖 Understanding Test Classifications

Design Tests (High Value) verify what the system does:

  • Assert on observable outputs, return values, or state changes
  • Cover error paths and boundary conditions
  • Would catch a behavioral regression if deleted
  • Remain valid even after internal refactoring

Implementation Tests (Low Value) verify how the system does it:

  • Assert on internal function calls (mocking internals)
  • Only test the happy path with typical inputs
  • Break during legitimate refactoring even when behavior is correct
  • Give false assurance: they pass even when the system is wrong

Goal: Shift toward tests that describe the system's behavioral contract — the promises it makes to its users and collaborators.

References:

🧪 Test quality analysis by Test Quality Sentinel · ● 592.9K ·

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

✅ Test Quality Sentinel: 85/100. Test quality is excellent — 0% of new tests are implementation tests (threshold: 30%). All 12 new tests verify behavioral contracts with strong edge-case coverage. Build tags present, no mock libraries, assertion messages throughout.

… safe outputs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

Commit pushed: 2f5ad19

🏗️ ADR gate enforced by Design Decision Gate 🏗️

@github-actions
Copy link
Copy Markdown
Contributor

🏗️ Design Decision Gate — ADR Required

This PR makes significant changes to core business logic (218 new lines in business logic directories) but does not have a linked Architecture Decision Record (ADR).

AI has analyzed the PR diff and generated a draft ADR to help you get started:

📄 Draft ADR: docs/adr/28486-protect-top-level-dot-folders-in-safe-outputs.md

What to do next

  1. Review the draft ADR committed to your branch — it was generated from the PR diff
  2. Complete the missing sections — add context the AI couldn't infer, refine the decision rationale, and list real alternatives you considered
  3. Commit the finalized ADR to docs/adr/ on your branch
  4. Reference the ADR in this PR body by adding a line such as:

    ADR: ADR-28486: Protect Any Top-Level Dot-Folder in Safe Outputs Handlers

Once an ADR is linked in the PR body, this gate will re-run and verify the implementation matches the decision.

Why ADRs Matter

"AI made me procrastinate on key design decisions. Because refactoring was cheap, I could always say 'I'll deal with this later.' Deferring decisions corroded my ability to think clearly."

ADRs create a searchable, permanent record of why the codebase looks the way it does. Future contributors (and your future self) will thank you.


📋 Michael Nygard ADR Format Reference

An ADR must contain these four sections to be considered complete:

  • Context — What is the problem? What forces are at play?
  • Decision — What did you decide? Why?
  • Alternatives Considered — What else could have been done?
  • Consequences — What are the trade-offs (positive and negative)?

All ADRs are stored in docs/adr/ as Markdown files numbered by PR number (e.g., 28486-protect-top-level-dot-folders-in-safe-outputs.md for PR #28486).

🔒 This PR cannot merge until an ADR is linked in the PR body.

🏗️ ADR gate enforced by Design Decision Gate 🏗️ · ● 166.3K ·

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR expands safe-output protected-file handling to also treat any top-level dot-directory (e.g. .vscode/, .cursor/, .devcontainer/) as protected by default for create-pull-request and push-to-pull-request-branch, while allowing selective opt-out via the existing protected-files.exclude list.

Changes:

  • Go: propagate a protect_top_level_dot_folders flag and compute protected_dot_folder_excludes from protected-files.exclude.
  • JS: implement top-level dot-folder detection in checkFileProtection with deduplication and add tests.
  • Repo maintenance: update docs and workflow lock files to include the new handler config flag.
Show a summary per file
File Description
pkg/workflow/safe_outputs_config_generation.go Adds protected_dot_folder_excludes to generated runtime config.json when dot-folders are excluded.
pkg/workflow/runtime_definitions.go Introduces getDotFolderExcludes() helper to extract dot-folder excludes.
pkg/workflow/compiler_safe_outputs_handlers.go Sets protect_top_level_dot_folders: true for create_pull_request and push_to_pull_request_branch.
pkg/workflow/compiler_safe_outputs_config.go Passes protected_dot_folder_excludes into handler-manager env config when applicable.
pkg/workflow/compiler_safe_outputs_config_test.go Adds Go tests for dot-folder exclude extraction and propagation + ensures new flag is present.
actions/setup/js/manifest_file_helpers.cjs Adds checkForTopLevelDotFolders() and integrates it into checkFileProtection with deduplication.
actions/setup/js/manifest_file_helpers.test.cjs Adds JS test coverage for dot-folder detection and protection behavior.
actions/setup/js/types/handler-factory.d.ts Extends handler config typings with protect_top_level_dot_folders and protected_dot_folder_excludes.
docs/src/content/docs/reference/safe-outputs-pull-requests.md Documents dot-folder protection and how exclude: interacts with it.
.github/workflows/weekly-safe-outputs-spec-review.lock.yml Regenerates locked safe-outputs config to include protect_top_level_dot_folders.
.github/workflows/weekly-editors-health-check.lock.yml Regenerates locked safe-outputs config to include protect_top_level_dot_folders.
.github/workflows/weekly-blog-post-writer.lock.yml Regenerates locked safe-outputs config to include protect_top_level_dot_folders.
.github/workflows/update-astro.lock.yml Regenerates locked safe-outputs config to include protect_top_level_dot_folders.
.github/workflows/unbloat-docs.lock.yml Regenerates locked safe-outputs config to include protect_top_level_dot_folders.
.github/workflows/ubuntu-image-analyzer.lock.yml Regenerates locked safe-outputs config to include protect_top_level_dot_folders.
.github/workflows/tidy.lock.yml Regenerates locked safe-outputs config to include protect_top_level_dot_folders (create + push handlers).
.github/workflows/test-create-pr-error-handling.lock.yml Regenerates locked safe-outputs config to include protect_top_level_dot_folders.
.github/workflows/technical-doc-writer.lock.yml Regenerates locked safe-outputs config to include protect_top_level_dot_folders.
.github/workflows/spec-extractor.lock.yml Regenerates locked safe-outputs config to include protect_top_level_dot_folders.
.github/workflows/spec-enforcer.lock.yml Regenerates locked safe-outputs config to include protect_top_level_dot_folders.
.github/workflows/smoke-update-cross-repo-pr.lock.yml Regenerates locked safe-outputs config to include protect_top_level_dot_folders (push handler).
.github/workflows/smoke-project.lock.yml Regenerates locked safe-outputs config to include protect_top_level_dot_folders.
.github/workflows/smoke-multi-pr.lock.yml Regenerates locked safe-outputs config to include protect_top_level_dot_folders.
.github/workflows/smoke-create-cross-repo-pr.lock.yml Regenerates locked safe-outputs config to include protect_top_level_dot_folders.
.github/workflows/smoke-claude.lock.yml Regenerates locked safe-outputs config to include protect_top_level_dot_folders (push handler).
.github/workflows/slide-deck-maintainer.lock.yml Regenerates locked safe-outputs config to include protect_top_level_dot_folders.
.github/workflows/schema-feature-coverage.lock.yml Regenerates locked safe-outputs config to include protect_top_level_dot_folders.
.github/workflows/refiner.lock.yml Regenerates locked safe-outputs config to include protect_top_level_dot_folders.
.github/workflows/q.lock.yml Regenerates locked safe-outputs config to include protect_top_level_dot_folders.
.github/workflows/poem-bot.lock.yml Regenerates locked safe-outputs config to include protect_top_level_dot_folders (create + push handlers).
.github/workflows/mergefest.lock.yml Regenerates locked safe-outputs config to include protect_top_level_dot_folders (push handler).
.github/workflows/layout-spec-maintainer.lock.yml Regenerates locked safe-outputs config to include protect_top_level_dot_folders.
.github/workflows/jsweep.lock.yml Regenerates locked safe-outputs config to include protect_top_level_dot_folders.
.github/workflows/instructions-janitor.lock.yml Regenerates locked safe-outputs config to include protect_top_level_dot_folders.
.github/workflows/hourly-ci-cleaner.lock.yml Regenerates locked safe-outputs config to include protect_top_level_dot_folders.
.github/workflows/go-logger.lock.yml Regenerates locked safe-outputs config to include protect_top_level_dot_folders.
.github/workflows/glossary-maintainer.lock.yml Regenerates locked safe-outputs config to include protect_top_level_dot_folders.
.github/workflows/github-mcp-tools-report.lock.yml Regenerates locked safe-outputs config to include protect_top_level_dot_folders.
.github/workflows/functional-pragmatist.lock.yml Regenerates locked safe-outputs config to include protect_top_level_dot_folders.
.github/workflows/dictation-prompt.lock.yml Regenerates locked safe-outputs config to include protect_top_level_dot_folders.
.github/workflows/developer-docs-consolidator.lock.yml Regenerates locked safe-outputs config to include protect_top_level_dot_folders.
.github/workflows/design-decision-gate.lock.yml Regenerates locked safe-outputs config to include protect_top_level_dot_folders (push handler).
.github/workflows/dead-code-remover.lock.yml Regenerates locked safe-outputs config to include protect_top_level_dot_folders.
.github/workflows/daily-workflow-updater.lock.yml Regenerates locked safe-outputs config to include protect_top_level_dot_folders.
.github/workflows/daily-safe-output-integrator.lock.yml Regenerates locked safe-outputs config to include protect_top_level_dot_folders.
.github/workflows/daily-rendering-scripts-verifier.lock.yml Regenerates locked safe-outputs config to include protect_top_level_dot_folders.
.github/workflows/daily-doc-updater.lock.yml Regenerates locked safe-outputs config to include protect_top_level_dot_folders.
.github/workflows/daily-doc-healer.lock.yml Regenerates locked safe-outputs config to include protect_top_level_dot_folders.
.github/workflows/daily-community-attribution.lock.yml Regenerates locked safe-outputs config to include protect_top_level_dot_folders.
.github/workflows/daily-astrostylelite-markdown-spellcheck.lock.yml Regenerates locked safe-outputs config to include protect_top_level_dot_folders.
.github/workflows/daily-architecture-diagram.lock.yml Regenerates locked safe-outputs config to include protect_top_level_dot_folders.
.github/workflows/craft.lock.yml Regenerates locked safe-outputs config to include protect_top_level_dot_folders (push handler).
.github/workflows/code-simplifier.lock.yml Regenerates locked safe-outputs config to include protect_top_level_dot_folders.
.github/workflows/code-scanning-fixer.lock.yml Regenerates locked safe-outputs config to include protect_top_level_dot_folders.
.github/workflows/cloclo.lock.yml Regenerates locked safe-outputs config to include protect_top_level_dot_folders.
.github/workflows/ci-coach.lock.yml Regenerates locked safe-outputs config to include protect_top_level_dot_folders.
.github/workflows/changeset.lock.yml Regenerates locked safe-outputs config to include protect_top_level_dot_folders (push handler).

Copilot's findings

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 57/58 changed files
  • Comments generated: 2

Comment thread .github/workflows/changeset.lock.yml Outdated
mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs
cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_382550279ab83ebb_EOF'
{"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"push_to_pull_request_branch":{"allowed_files":[".changeset/**"],"commit_title_suffix":" [skip-ci]","if_no_changes":"warn","max_patch_size":1024,"patch_format":"bundle","protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"protected_path_prefixes":[".github/",".agents/",".githooks/",".husky/",".codex/"]},"report_incomplete":{},"update_pull_request":{"allow_body":true,"allow_title":false,"default_operation":"append","max":1,"update_branch":false}}
{"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"push_to_pull_request_branch":{"allowed_files":[".changeset/**"],"commit_title_suffix":" [skip-ci]","if_no_changes":"warn","max_patch_size":1024,"patch_format":"bundle","protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"protected_path_prefixes":[".github/",".agents/",".githooks/",".husky/",".codex/"]},"report_incomplete":{},"update_pull_request":{"allow_body":true,"allow_title":false,"default_operation":"append","max":1,"update_branch":false}}
Copy link

Copilot AI Apr 25, 2026

Choose a reason for hiding this comment

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

This workflow allowlists ".changeset/**" for push_to_pull_request_branch, but with protect_top_level_dot_folders: true and no protected_dot_folder_excludes, any patch touching .changeset/... will now be treated as protected and (with the default protected-files policy) denied/fallback unexpectedly. If this workflow is intended to update changesets, add .changeset/ to the protected-files exclude list (so it flows into protected_dot_folder_excludes) or otherwise opt this directory out explicitly.

Suggested change
{"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"push_to_pull_request_branch":{"allowed_files":[".changeset/**"],"commit_title_suffix":" [skip-ci]","if_no_changes":"warn","max_patch_size":1024,"patch_format":"bundle","protect_top_level_dot_folders":true,"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"protected_path_prefixes":[".github/",".agents/",".githooks/",".husky/",".codex/"]},"report_incomplete":{},"update_pull_request":{"allow_body":true,"allow_title":false,"default_operation":"append","max":1,"update_branch":false}}
{"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"push_to_pull_request_branch":{"allowed_files":[".changeset/**"],"commit_title_suffix":" [skip-ci]","if_no_changes":"warn","max_patch_size":1024,"patch_format":"bundle","protect_top_level_dot_folders":true,"protected_dot_folder_excludes":[".changeset/"],"protected_files":["package.json","bun.lockb","bunfig.toml","deno.json","deno.jsonc","deno.lock","global.json","NuGet.Config","Directory.Packages.props","mix.exs","mix.lock","go.mod","go.sum","stack.yaml","stack.yaml.lock","pom.xml","build.gradle","build.gradle.kts","settings.gradle","settings.gradle.kts","gradle.properties","package-lock.json","yarn.lock","pnpm-lock.yaml","npm-shrinkwrap.json","requirements.txt","Pipfile","Pipfile.lock","pyproject.toml","setup.py","setup.cfg","Gemfile","Gemfile.lock","uv.lock","CODEOWNERS","DESIGN.md","AGENTS.md","CLAUDE.md","GEMINI.md"],"protected_path_prefixes":[".github/",".agents/",".githooks/",".husky/",".codex/"]},"report_incomplete":{},"update_pull_request":{"allow_body":true,"allow_title":false,"default_operation":"append","max":1,"update_branch":false}}

Copilot uses AI. Check for mistakes.
Comment on lines +221 to +230
// getDotFolderExcludes returns the subset of excludeFiles that are top-level
// dot-folder path prefixes (i.e. start with "." and end with "/").
// These are used at compile time to tell the runtime handler which specific
// dot-folders have been opted out of the general top-level-dot-folder protection.
func getDotFolderExcludes(excludeFiles []string) []string {
var result []string
for _, f := range excludeFiles {
// Must start with ".", end with "/", and have at least one char between
// them (e.g. ".agents/" is valid; "./" is not).
if len(f) > 2 && f[0] == '.' && f[len(f)-1] == '/' {
Copy link

Copilot AI Apr 25, 2026

Choose a reason for hiding this comment

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

getDotFolderExcludes is documented as returning top-level dot-folder prefixes, but the current check only verifies "starts with '.'" and "ends with '/'". That will also include nested prefixes like .github/workflows/, which will be forwarded as protected_dot_folder_excludes but will never match the runtime check (which only compares firstComponent + "/"). Consider tightening the predicate to only accept a single path component (e.g., .name/ with no additional /) and optionally deduplicating results.

Copilot uses AI. Check for mistakes.
…fixes

Dot-folder prefixes (.github/, .agents/, .githooks/, .husky/ and any
engine-specific ones like .claude/) are now all covered by the general
protect_top_level_dot_folders rule and do not need to appear in the
explicit protected_path_prefixes list. Remove them from
getProtectedPathPrefixes() and skip setting the key when the computed
list is empty. Update affected tests accordingly.

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/76239f17-a51b-4871-a6d6-742730986aa5

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 25, 2026

@copilot remove the protected paths that start with '.' from the computed list since they are protected by default

Done in commit 4a832c4.

getProtectedPathPrefixes() now drops all dot-prefix entries — both the hardcoded ones (.github/, .agents/, .githooks/, .husky/) and any engine-specific extras (e.g. .claude/) — since they're all covered by the new protect_top_level_dot_folders general rule. The protected_path_prefixes key is also omitted from the runtime config when the computed list is empty.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • https://api.github.com/graphql
    • Triggering command: /usr/bin/gh /usr/bin/gh api graphql -f query=query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { hasDiscussionsEnabled } } -f owner=github -f name=gh-aw (http block)
    • Triggering command: /usr/bin/gh /usr/bin/gh api graphql -f query=query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { hasDiscussionsEnabled } } -f owner=github -f name=gh-aw 54DilN-/FShD_hRn-1 (http block)
    • Triggering command: /usr/bin/gh /usr/bin/gh api graphql -f query=query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { hasDiscussionsEnabled } } -f owner=github -f name=gh-aw _test.go (http block)
  • https://api.github.com/orgs/test-owner/actions/secrets
    • Triggering command: /usr/bin/gh gh api /orgs/test-owner/actions/secrets --jq .secrets[].name get --local erifier.lock.yml nore (http block)
  • https://api.github.com/repos/actions/ai-inference/git/ref/tags/v1
    • Triggering command: /usr/bin/gh gh api /repos/actions/ai-inference/git/ref/tags/v1 --jq [.object.sha, .object.type] | @tsv --get-regexp ^remote\..*\.gh-resolved$ /usr/bin/git 236474865/.githugit rev-parse 64/pkg/tool/linu--show-toplevel git rev-�� --show-toplevel 64/pkg/tool/linux_amd64/vet /usr/bin/git k/gh-aw/gh-aw rev-parse kflows/mergefestinstall git (http block)
  • https://api.github.com/repos/actions/checkout/git/ref/tags/v3
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v3 --jq [.object.sha, .object.type] | @tsv --get remote.origin.url /usr/bin/git 1 gpg.program r: $owner, name:--show-toplevel git conf�� user.name Test User (http block)
  • https://api.github.com/repos/actions/checkout/git/ref/tags/v5
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v5 --jq [.object.sha, .object.type] | @tsv sRemoteWithRealGitcustom_branch4019031934/001 sRemoteWithRealGitcustom_branch4019031934/002/work .cfg remote.origin.urgit (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v5 --jq [.object.sha, .object.type] | @tsv --show-toplevel /usr/bin/gh /usr/bin/git archie.md erena-mcp-serverrev-parse (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v5 --jq [.object.sha, .object.type] | @tsv --show-toplevel node /usr/bin/git /tmp/TestHashCongit git /opt/hostedtoolc--show-toplevel git rev-�� --show-toplevel node /usr/bin/git /tmp/TestHashStagit s/12346/artifactrev-parse /usr/bin/git git (http block)
  • https://api.github.com/repos/actions/checkout/git/ref/tags/v6
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v6 --jq [.object.sha, .object.type] | @tsv --show-toplevel 64/pkg/tool/linuconfig /usr/bin/git _.a --local (http block)
  • https://api.github.com/repos/actions/github-script/git/ref/tags/v8
    • Triggering command: /usr/bin/gh gh api /repos/actions/github-script/git/ref/tags/v8 --jq [.object.sha, .object.type] | @tsv --show-toplevel git /usr/bin/git OnlyCompiledOutpbash config ache/go/1.25.8/xnew (upstream) git rev-�� --show-toplevel ache/go/1.25.8/x/tmp/gh-aw-merge-1931253411/new.md /usr/bin/git FieldEnforcementgit show /usr/bin/git git (http block)
  • https://api.github.com/repos/actions/github-script/git/ref/tags/v9
    • Triggering command: /usr/bin/gh gh api /repos/actions/github-script/git/ref/tags/v9 --jq [.object.sha, .object.type] | @tsv --get-regexp --local $name) { hasDiscussionsEnabled } } (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/github-script/git/ref/tags/v9 --jq [.object.sha, .object.type] | @tsv tted&#34; --get bash (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/github-script/git/ref/tags/v9 --jq [.object.sha, .object.type] | @tsv */*.json&#39; &#39;!../.-f --local repository(owne-f credential.helpeinfocmp (http block)
  • https://api.github.com/repos/actions/setup-go/git/ref/tags/v4
    • Triggering command: /usr/bin/gh gh api /repos/actions/setup-go/git/ref/tags/v4 --jq [.object.sha, .object.type] | @tsv (http block)
  • https://api.github.com/repos/actions/setup-node/git/ref/tags/v4
    • Triggering command: /usr/bin/gh gh api /repos/actions/setup-node/git/ref/tags/v4 --jq [.object.sha, .object.type] | @tsv --show-toplevel ache/go/1.25.8/xconfig /usr/bin/git 3233392951/001&#39; 3233392951/001&#39; ock.yml git rev-�� --show-toplevel git /usr/bin/gh ithub/workflows committer.name in/node gh (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/setup-node/git/ref/tags/v4 --jq [.object.sha, .object.type] | @tsv -3785375722/base.md -3785375722/new.md /usr/bin/git e-analyzer.md (http block)
  • https://api.github.com/repos/actions/upload-artifact/git/ref/tags/v4
    • Triggering command: /usr/bin/gh gh api /repos/actions/upload-artifact/git/ref/tags/v4 --jq [.object.sha, .object.type] | @tsv /tmp/TestHashConsistency_GoAndJavaScript1720260103/001/test-simpremote.origin.url (http block)
  • https://api.github.com/repos/astral-sh/setup-uv/git/ref/tags/eac588ad8def6316056a12d4907a9d4d84ff7a3b
    • Triggering command: /usr/bin/gh gh api /repos/astral-sh/setup-uv/git/ref/tags/eac588ad8def6316056a12d4907a9d4d84ff7a3b --jq [.object.sha, .object.type] | @tsv (http block)
    • Triggering command: /usr/bin/gh gh api /repos/astral-sh/setup-uv/git/ref/tags/eac588ad8def6316056a12d4907a9d4d84ff7a3b --jq [.object.sha, .object.type] | @tsv h ../../../.prettierignore (http block)
  • https://api.github.com/repos/github/gh-aw
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw --jq .default_branch */*.ts&#39; &#39;**/*.json&#39; --ignore-path ../../../.pret.prettierignore (http block)
  • https://api.github.com/repos/github/gh-aw-actions/git/ref/tags/v0.1.2
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw-actions/git/ref/tags/v0.1.2 --jq [.object.sha, .object.type] | @tsv run-script/lib/n--show-toplevel sh /usr/bin/gh &#34;prettier&#34; --wrigit (http block)
  • https://api.github.com/repos/github/gh-aw-actions/git/ref/tags/v1.0.0
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw-actions/git/ref/tags/v1.0.0 --jq [.object.sha, .object.type] | @tsv /tmp/TestHashConsistency_GoAndJavaScript1720260103/001/test-complex-frontmatter-with-tools.md 64/pkg/tool/linurev-parse /usr/bin/git get --local k/_temp/uv-pytho--show-toplevel git -C runs/20260425-183934-9726/test-2834641330 git /usr/bin/gh s/test.md user.name repository(owne--show-toplevel gh (http block)
  • https://api.github.com/repos/github/gh-aw-actions/git/ref/tags/v1.2.3
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw-actions/git/ref/tags/v1.2.3 --jq [.object.sha, .object.type] | @tsv image:v1.0.0 ache/go/1.25.8/xconfig ps get --local repository(owne--show-toplevel ps git git git /usr/bin/git ithub/workflows user.name or.lock.yml git (http block)
  • https://api.github.com/repos/github/gh-aw/actions/runs/1/artifacts
    • Triggering command: /usr/bin/gh gh api --paginate repos/{owner}/{repo}/actions/runs/1/artifacts --jq .artifacts[].name config me: String!) { repository(owner: $owner, name:-f remote.origin.urgit (http block)
    • Triggering command: /usr/bin/gh gh run download 1 --dir test-logs/run-1 config er: String!, $name: String!) { repository(owner: $owner, name:-f remote.origin.urgit (http block)
  • https://api.github.com/repos/github/gh-aw/actions/runs/12345/artifacts
    • Triggering command: /usr/bin/gh gh api --paginate repos/{owner}/{repo}/actions/runs/12345/artifacts --jq .artifacts[].name rev-parse me: String!) { repository(owner: $owner, name: $name) { hasDiscussionsEnabled } } (http block)
    • Triggering command: /usr/bin/gh gh run download 12345 --dir test-logs/run-12345 :latest kflows/daily-team-status.lock.yml -f owner=github -f git -C ithub/workflows show /usr/bin/git ath ../../../.prgit (http block)
  • https://api.github.com/repos/github/gh-aw/actions/runs/12346/artifacts
    • Triggering command: /usr/bin/gh gh api --paginate repos/{owner}/{repo}/actions/runs/12346/artifacts --jq .artifacts[].name :latest /usr/bin/git (http block)
    • Triggering command: /usr/bin/gh gh run download 12346 --dir test-logs/run-12346 mLsRemoteWithRealGitcustom_branch4019031934/001&#39; me: String!) { repository(owner: $owner, name: $name) { hasDiscussionsEnabled } } (http block)
  • https://api.github.com/repos/github/gh-aw/actions/runs/2/artifacts
    • Triggering command: /usr/bin/gh gh api --paginate repos/{owner}/{repo}/actions/runs/2/artifacts --jq .artifacts[].name show /usr/bin/git (http block)
    • Triggering command: /usr/bin/gh gh run download 2 --dir test-logs/run-2 rev-parse me: String!) { repository(owner: $owner, name: $name) { hasDiscussionsEnabled } } (http block)
  • https://api.github.com/repos/github/gh-aw/actions/runs/3/artifacts
    • Triggering command: /usr/bin/gh gh api --paginate repos/{owner}/{repo}/actions/runs/3/artifacts --jq .artifacts[].name LsRemoteWithRealGitcustom_branch4019031934/001&#39; /usr/bin/git l owner=github -f git -C ithout_min-integrity287142601/001 config (http block)
    • Triggering command: /usr/bin/gh gh run download 3 --dir test-logs/run-3 show /usr/bin/infocmp l (http block)
  • https://api.github.com/repos/github/gh-aw/actions/runs/4/artifacts
    • Triggering command: /usr/bin/gh gh api --paginate repos/{owner}/{repo}/actions/runs/4/artifacts --jq .artifacts[].name (http block)
    • Triggering command: /usr/bin/gh gh run download 4 --dir test-logs/run-4 LsRemoteWithRealGitbranch_with_hyphen2108291466/001&#39; kflows/deep-report.lock.yml -f owner=github -f git -C mpiledOutput181272226/001 rev-parse /usr/bin/gh h ../../../.pret/opt/hostedtoolcache/go/1.25.8/x64/pkg/tool/linux_amd64/vet (http block)
  • https://api.github.com/repos/github/gh-aw/actions/runs/5/artifacts
    • Triggering command: /usr/bin/gh gh api --paginate repos/{owner}/{repo}/actions/runs/5/artifacts --jq .artifacts[].name er@0.32.0 sh.lock.yml -f owner=github -f /usr/bin/gh api ithout_min-integrity287142601/001 est me: String!) { repository(owner: $owner, name:-nilfunc -f owner=github -f git (http block)
    • Triggering command: /usr/bin/gh gh run download 5 --dir test-logs/run-5 (http block)
  • https://api.github.com/repos/github/gh-aw/actions/workflows
    • Triggering command: /usr/bin/gh gh workflow list --json name,state,path /\1/p --local repository(owne-f nore (http block)
    • Triggering command: /usr/bin/gh gh run list --json databaseId,number,url,status,conclusion,workflowName,createdAt,startedAt,updatedAt,event,headBranch,headSha,displayTitle --workflow nonexistent-workflow-12345 --limit 100 (http block)
    • Triggering command: /usr/bin/gh gh run list --json databaseId,number,url,status,conclusion,workflowName,createdAt,startedAt,updatedAt,event,headBranch,headSha,displayTitle --workflow nonexistent-workflow-12345 --limit 6 (http block)
  • https://api.github.com/repos/github/gh-aw/contents/.github/workflows/shared/reporting.md
    • Triggering command: /tmp/go-build1522338287/b404/cli.test /tmp/go-build1522338287/b404/cli.test -test.testlogfile=/tmp/go-build1522338287/b404/testlog.txt -test.paniconexit0 -test.v=true -test.parallel=4 -test.timeout=10m0s -test.run=^Test -test.short=true (http block)
  • https://api.github.com/repos/github/gh-aw/git/ref/tags/v0.47.4
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw/git/ref/tags/v0.47.4 --jq [.object.sha, .object.type] | @tsv --show-toplevel infocmp /usr/bin/git vaScript17202601git (http block)
  • https://api.github.com/repos/github/gh-aw/git/ref/tags/v1.0.0
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw/git/ref/tags/v1.0.0 --jq [.object.sha, .object.type] | @tsv /home/REDACTED/work/gh-aw/gh-aw/.github/workflows config 64/pkg/tool/linux_amd64/vet remote.origin.urgit (http block)
  • https://api.github.com/repos/github/gh-aw/git/ref/tags/v1.2.3
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw/git/ref/tags/v1.2.3 --jq [.object.sha, .object.type] | @tsv rite &#39;../../../**/*.json&#39; &#39;!../../../pkg/workflo-f copilot/add-top-level-folder-protection /opt/pipx_bin/bash (http block)
  • https://api.github.com/repos/github/gh-aw/git/ref/tags/v2.0.0
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw/git/ref/tags/v2.0.0 --jq [.object.sha, .object.type] | @tsv --verify copilot/add-top-level-folder-protection repository(owner: $owner, name: $name) { hasDiscussionsEnabled } } rkflow/js/**/*.jnode (http block)
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw/git/ref/tags/v2.0.0 --jq [.object.sha, .object.type] | @tsv copilot/add-top-level-folder-protection^{commit}remote.origin.url (http block)
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw/git/ref/tags/v2.0.0 --jq [.object.sha, .object.type] | @tsv ithub/workflows k/gh-aw/gh-aw/pk-ifaceassert repository(owne-nilfunc rkflow/js/**/*.jgit (http block)
  • https://api.github.com/repos/github/gh-aw/git/ref/tags/v3.0.0
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw/git/ref/tags/v3.0.0 --jq [.object.sha, .object.type] | @tsv copilot/add-top-level-folder-proremote.origin.url (http block)
  • https://api.github.com/repos/nonexistent/action/git/ref/tags/v999.999.999
    • Triggering command: /usr/bin/gh gh api /repos/nonexistent/action/git/ref/tags/v999.999.999 --jq [.object.sha, .object.type] | @tsv /home/REDACTED/work/gh-aw/gh-aw/.github/workflows rev-parse 64/pkg/tool/linux_amd64/vet logs/user.env (http block)
  • https://api.github.com/repos/nonexistent/repo/actions/runs/12345
    • Triggering command: /usr/bin/gh gh run view 12345 --repo nonexistent/repo --json status,conclusion remote.origin.urgit (http block)
  • https://api.github.com/repos/owner/repo/actions/workflows
    • Triggering command: /usr/bin/gh gh workflow list --json name,state,path --repo owner/repo k/node_modules/.bin/sh nore (http block)
    • Triggering command: /usr/bin/gh gh workflow list --json name,state,path --repo owner/repo ache/node/24.14.1/x64/lib/node_m-f nore (http block)
    • Triggering command: /usr/bin/gh gh workflow list --repo owner/repo --json name,path,state (http block)
  • https://api.github.com/repos/test-owner/test-repo/actions/secrets
    • Triggering command: /usr/bin/gh gh api /repos/test-owner/test-repo/actions/secrets --jq .secrets[].name 371587/b001/work-f --local repository(owne-f nore (http block)
  • https://api.github.com/repos/test/repo
    • Triggering command: /usr/bin/gh gh api /repos/test/repo --jq .default_branch 1/test1.md config generator.lock.yml l (http block)

If you need me to access, download, or install something from one of these locations, you can either:

@pelikhan pelikhan merged commit 9908d14 into main Apr 25, 2026
29 checks passed
@pelikhan pelikhan deleted the copilot/add-top-level-folder-protection branch April 25, 2026 18:51
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.

3 participants