feat: protect any top-level folder starting with '.' in safe outputs#28486
feat: protect any top-level folder starting with '.' in safe outputs#28486
Conversation
- 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 remove the protected paths that start with '.' from the computed list since they are protected by default |
🧪 Test Quality Sentinel ReportTest Quality Score: 85/100✅ Excellent test quality
Test Classification DetailsView All Test Classifications (12 tests)
Flagged Tests — Requires Reviewi️ Test Inflation —
|
There was a problem hiding this comment.
✅ 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>
|
Commit pushed:
|
🏗️ Design Decision Gate — ADR RequiredThis 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: What to do next
Once an ADR is linked in the PR body, this gate will re-run and verify the implementation matches the decision. Why ADRs Matter
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 ReferenceAn ADR must contain these four sections to be considered complete:
All ADRs are stored in
|
There was a problem hiding this comment.
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_foldersflag and computeprotected_dot_folder_excludesfromprotected-files.exclude. - JS: implement top-level dot-folder detection in
checkFileProtectionwith 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
| 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}} |
There was a problem hiding this comment.
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.
| {"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}} |
| // 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] == '/' { |
There was a problem hiding this comment.
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.
…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>
Done in commit 4a832c4.
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:
If you need me to access, download, or install something from one of these locations, you can either:
|
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 thecreate-pull-requestandpush-to-pull-request-branchsafe 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: AddgetDotFolderExcludes()helper that extracts dot-folder path prefixes (e.g..cursor/) from an exclusion listpkg/workflow/compiler_safe_outputs_handlers.go: Addprotect_top_level_dot_folders: trueto bothcreate_pull_requestandpush_to_pull_request_branchhandler configspkg/workflow/compiler_safe_outputs_config.go+safe_outputs_config_generation.go: Compute and passprotected_dot_folder_excludesso the runtime handler respects per-folder exclusions from theexclude:listJavaScript (runtime)
actions/setup/js/manifest_file_helpers.cjs: AddcheckForTopLevelDotFolders()and updatecheckFileProtection()to apply the new rule with deduplicationactions/setup/js/types/handler-factory.d.ts: Addprotect_top_level_dot_foldersandprotected_dot_folder_excludestoHandlerConfigTests
TestGetDotFolderExcludes,TestProtectTopLevelDotFolders,TestProtectedDotFolderExcludes,TestNoProtectedDotFolderExcludesWhenNoneDotFolderExcludedcheckForTopLevelDotFoldersdescribe block + newcheckFileProtectiondot-folder test cases (62 tests pass)Docs
safe-outputs-pull-requests.mdwith a "What Is Protected" section and clarified how theexclude:list interacts with the new dot-folder ruleExclusion behaviour
Users can still opt specific dot-folders out of protection via the existing
exclude:list:Dot-folder entries in the
exclude:list are forwarded to the runtime asprotected_dot_folder_excludesso the general rule skips them correctly.