Skip to content

fix: tools.github: false does not override tools.github.* from shared imports#26323

Merged
pelikhan merged 6 commits intomainfrom
copilot/fix-tools-github-override
Apr 15, 2026
Merged

fix: tools.github: false does not override tools.github.* from shared imports#26323
pelikhan merged 6 commits intomainfrom
copilot/fix-tools-github-override

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 14, 2026

When a main workflow sets tools.github: false, imported shared workflows with tools.github: mode: remote (or any other github tool config) silently win, leaving the GitHub MCP server enabled against the user's explicit intent.

Root cause

parser.MergeTools(base, additional) had a fallback that overwrote the base value with the additional value on type mismatches (e.g. bool vs map):

} else {
    // Not both same type, overwrite with new value
    result[key] = newValue  // imports override main workflow — wrong
}

base is the main workflow; additional is the import. The base value already lives in result after maps.Copy(result, base), so the overwrite was backwards.

In addition, the map+allowed-array merge path was copying the import map on top of the base map (maps.Copy(mergedMap, newMap) last), which let imported scalar fields (e.g. mode) overwrite base scalar fields even when the base explicitly set them.

Changes

  • pkg/parser/tools_merger.go — Remove the result[key] = newValue overwrite in the type-mismatch branch. Base value wins for all non-array, non-map conflicts (type mismatch and same scalar type), consistent with the documented "workflow overrides imports" principle. Fix the allowed-array merge path to write the import map first and the base map second, so base scalar fields win while import-only keys are still contributed and allowed arrays are concatenated.
  • pkg/parser/frontmatter_merge_test.go — Add unit tests: false overrides map, map not overridden by false, nested scalar base-wins; update objects with overlapping keys expectation to match new semantics.
  • pkg/workflow/importable_tools_test.go — Add TestGitHubToolFalseOverridesImport: end-to-end compilation of a workflow with tools.github: false importing a shared workflow with tools.github: mode: remote, asserting all GitHub MCP server indicators (X-MCP-Toolsets, api.githubcopilot.com/mcp/, ghcr.io/github/github-mcp-server) are absent from the compiled output.

[!WARNING]

…orts

In parser.MergeTools, when both base and additional have the same key but
types differ (or for same non-array, non-map type conflicts), remove the
overwrite that replaced the base value with the additional value. The base
value (main workflow) already sits in result from maps.Copy and should take
precedence over the additional (imported) value, consistent with the
documented 'workflow overrides imports' principle.

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/65d4ab13-b9a5-4b1f-9521-cac1f0699508

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix tools.github override not respected in shared import fix: tools.github: false does not override tools.github.* from shared imports Apr 14, 2026
Copilot AI requested a review from pelikhan April 14, 2026 23:52
@pelikhan pelikhan marked this pull request as ready for review April 14, 2026 23:54
Copilot AI review requested due to automatic review settings April 14, 2026 23:54
@github-actions
Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 90/100

Excellent test quality

Metric Value
New/modified tests analyzed 4
✅ Design tests (behavioral contracts) 4 (100%)
⚠️ Implementation tests (low value) 0 (0%)
Tests with error/edge cases 4 (100%)
Duplicate test clusters 0
Test inflation detected Yes (118 test lines vs 2 production lines — justified for a targeted bug fix)
🚨 Coding-guideline violations None

Test Classification Details

Test File Classification Issues Detected
TestMergeTools — row: "base false overrides additional map (tools.github: false)" pkg/parser/frontmatter_merge_test.go ✅ Design None — directly encodes the bug contract
TestMergeTools — row: "base map not overridden by additional false" pkg/parser/frontmatter_merge_test.go ✅ Design None — tests the symmetric inverse
TestMergeTools — row: "base scalar values override additional scalars in nested maps" pkg/parser/frontmatter_merge_test.go ✅ Design None — documents general merge precedence
TestGitHubToolFalseOverridesImport pkg/workflow/importable_tools_test.go ✅ Design None — end-to-end behavioral verification

Note: One existing TestMergeToolsFromJSON case had its expected value corrected ("config":"new""config":"old") to align with the fixed semantics. This is a test fix, not a new test, and is a positive sign of test-first discipline.


Flagged Tests — Requires Review

None — all tests are high-quality behavioral tests.


Language Support

Tests analyzed:

  • 🐹 Go (*_test.go): 4 tests — unit (//go:build !integration)
  • 🟨 JavaScript (*.test.cjs, *.test.js): 0 tests

Score Breakdown

Component Score Notes
Behavioral Coverage (40 pts) 40 All 4 new tests are design tests
Error/Edge Case Coverage (30 pts) 30 All 4 tests exercise the edge-case behavior
Low Duplication (20 pts) 20 No duplicate clusters
Proportional Growth (10 pts) 0 118 test lines / 2 production lines = 59:1 (exceeds 2:1 threshold)
Total 90 Inflation flag is expected for a precisely-scoped bug fix

i️ The inflation ratio (59:1) is technically above the 2:1 threshold triggering a 10-point deduction, but this is a well-known pattern in good TDD: a small, surgical fix in production code (tools_merger.go, 2 lines changed) is paired with a comprehensive test suite. The test growth is proportional to the complexity of the behavioral contract being enforced, not padding.


Verdict

Check passed. 0% of new tests are implementation tests (threshold: 30%). All four new/modified test scenarios directly encode the behavioral contract that tools.github: false in the base workflow must override any tools.github.* configuration from shared imports — at both the unit level (MergeTools()) and the end-to-end compilation level (TestGitHubToolFalseOverridesImport). No coding-guideline violations detected.


📖 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.

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

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: 90/100. Test quality is excellent — 0% of new tests are implementation tests (threshold: 30%). All 4 new test scenarios enforce the behavioral contract that tools.github: false overrides imported configurations, tested at both unit and end-to-end levels.

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

Fixes a merge-precedence bug where tools.github: false in a main workflow could be overridden by imported workflows that define tools.github as an object (e.g., mode: remote), unintentionally enabling the GitHub MCP server.

Changes:

  • Adjust parser.MergeTools so base (main workflow) values win on type mismatches and scalar conflicts (imports can’t override).
  • Add unit tests covering false vs map precedence and nested scalar base-wins behavior; update overlapping-keys expectation for JSON tool merges.
  • Add an end-to-end compilation test ensuring tools.github: false prevents GitHub MCP header emission when an import enables remote mode.
Show a summary per file
File Description
pkg/parser/tools_merger.go Changes merge behavior so base workflow values are not overwritten on type mismatches / scalar conflicts.
pkg/parser/frontmatter_merge_test.go Adds/updates unit tests to lock in base-wins semantics for tool merges.
pkg/workflow/importable_tools_test.go Adds integration-style test asserting tools.github: false disables GitHub MCP output even when imports enable it.

Copilot's findings

Tip

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

  • Files reviewed: 3/3 changed files
  • Comments generated: 2

Comment on lines 149 to +152
result[key] = recursiveMerged
} else {
// Not both same type, overwrite with new value
result[key] = newValue
}
// Type mismatch (or same non-array, non-map type): base value takes precedence.
// result[key] already contains existingValue from maps.Copy above — no action needed.
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

In the map+"allowed" merge path above, maps.Copy(mergedMap, newMap) means imported tool configs can overwrite other scalar fields from the base workflow whenever both maps include an allowed key (e.g., tools.github.mode could be overridden if both base and import specify allowed). This contradicts the documented "workflow overrides imports" semantics. Consider merging maps in a base-wins way (e.g., recursive merge that keeps existing scalar values) while still concatenating/deduping allowed.

Copilot uses AI. Check for mistakes.
Comment thread pkg/workflow/importable_tools_test.go Outdated
Comment on lines +831 to +833
if strings.Contains(workflowData, "X-MCP-Toolsets") {
t.Error("Expected compiled workflow to NOT contain X-MCP-Toolsets (github MCP server should be disabled by tools.github: false)")
}
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

This assertion only checks for the X-MCP-Toolsets header, which is specific to GitHub remote mode. To make the test robust against future renderer changes (or a scenario where the GitHub MCP server is still enabled but without that header), also assert that GitHub MCP server indicators are absent (e.g., the remote URL api.githubcopilot.com/mcp/ and/or the local container image ghcr.io/github/github-mcp-server).

Suggested change
if strings.Contains(workflowData, "X-MCP-Toolsets") {
t.Error("Expected compiled workflow to NOT contain X-MCP-Toolsets (github MCP server should be disabled by tools.github: false)")
}
githubMCPIndicators := []string{
"X-MCP-Toolsets",
"api.githubcopilot.com/mcp/",
"ghcr.io/github/github-mcp-server",
}
for _, indicator := range githubMCPIndicators {
if strings.Contains(workflowData, indicator) {
t.Errorf("Expected compiled workflow to NOT contain GitHub MCP server indicator %q (github MCP server should be disabled by tools.github: false)", indicator)
}
}

Copilot uses AI. Check for mistakes.
…tics

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

Commit pushed: 591fbde

🏗️ 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 (120 new lines in pkg/) 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/26323-tools-config-base-wins-on-type-conflict.md

The draft captures the core design decision: MergeTools must give the base (main workflow) value unconditional precedence over imported values for all scalar conflicts, including type mismatches — so that tools.github: false reliably disables a tool even when an import declares the same key as a map.

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 confirm/replace the alternatives with the ones you actually considered
  3. Reference the ADR in this PR body by adding a line such as:

    ADR: ADR-26323: Tools Config Base-Wins on Type Conflict

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., 26323-tools-config-base-wins-on-type-conflict.md for PR #26323).

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

Note

🔒 Integrity filter blocked 1 item

The following item were blocked because they don't meet the GitHub integrity level.

To allow these resources, lower min-integrity in your GitHub frontmatter:

tools:
  github:
    min-integrity: approved  # merged | approved | unapproved | none

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

@pelikhan
Copy link
Copy Markdown
Collaborator

@copilot review all comments

… test assertions

- In the tools_merger.go allowed-array merge path, reverse the maps.Copy
  order so import values are written first and base values overwrite them,
  preserving base-wins semantics for all scalar fields
- In TestGitHubToolFalseOverridesImport, check all three GitHub MCP server
  indicators (X-MCP-Toolsets, api.githubcopilot.com/mcp/, ghcr.io/github/
  github-mcp-server) to make the assertion robust against renderer changes

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/0e289010-b2b3-408e-8bcd-995f8a293a0a

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

Copilot AI commented Apr 15, 2026

@copilot review all comments

Both review comments addressed in 6c51636:

  1. tools_merger.go allowed-array merge path — Reversed the maps.Copy order so import values are written first and base values overwrite them. Base map now wins for all scalar fields (e.g. tools.github.mode) even when both maps contain an allowed key.

  2. TestGitHubToolFalseOverridesImport assertion — Expanded to check all three GitHub MCP server indicators: X-MCP-Toolsets, api.githubcopilot.com/mcp/, and ghcr.io/github/github-mcp-server.

@pelikhan
Copy link
Copy Markdown
Collaborator

@copilot the GitHub MCP server should merge unless it is "false" in the main workflows, which turns it off.

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 15, 2026

@copilot the GitHub MCP server should merge unless it is "false" in the main workflows, which turns it off.

The current implementation (6c51636) already behaves this way:

  1. tools.github: false in main workflow → disables; import map can't re-enable it.
  2. Both have map configs → merges: base scalar fields win for conflicts (e.g. mode: local beats import's mode: remote), but import contributes keys the base doesn't have (e.g. toolsets) and allowed arrays are concatenated.
  3. Main workflow has no mode, import provides one → import's value fills in (no conflict).

The reversal of maps.Copy order in the allowed-array path (the latest commit) preserves this merge behavior while fixing the base-wins semantics for scalar conflicts within those maps.

@pelikhan
Copy link
Copy Markdown
Collaborator

@copilot review changes in lock files, specially actions cache ids

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 15, 2026

@copilot review changes in lock files, specially actions cache ids

All lock file changes are correct side effects of the merge fix and were regenerated by make recompile in 6c51636:

copilot-pr-merged-report.lock.yml (largest change): tools.github: false in the main workflow now correctly disables GitHub MCP. Before the fix, the import copilot-pr-analysis-base.md (which has tools.github: toolsets: [default]) was overriding the main workflow's false. After the fix the false wins, removing the github-mcp-server container, the determine-automatic-lockdown step, and the GITHUB_MCP_SERVER_TOKEN env var.

Cache key changes (9 workflows): The pattern is consistent — all changes come from tools.cache-memory: {key: "specific-name"} in a parent import no longer being overwritten by a child import's generic tools.cache-memory: true. For example:

  • copilot-session-insights: copilot-session-data-fetch.md sets key: copilot-session-data; before the fix python-dataviz.md's cache-memory: true overrode this → default key. After the fix the specific key is preserved.
  • python-data-charts: charts-with-trending.md sets key: charts-trending-…; same pattern.

All resulting cache keys are correct. The direction of each change is: more specific parent-import configuration now wins over a generic child-import true, consistent with the "base wins" semantics.

cloclo.lock.yml and video-analyzer.lock.yml: Both main workflows have bash: true (all bash). Before the fix, imports' specific command lists were overriding true. After the fix, bash: true from the main/parent workflow wins, producing Bash (unrestricted) instead of the filtered command list.

@github-actions
Copy link
Copy Markdown
Contributor

Hey @Copilot 👋 — great work tracking down the tools.github: false override bug! The root-cause analysis is detailed and precise, the fix in tools_merger.go is minimal and surgical, and the test coverage (unit + end-to-end) is exactly what this kind of merge-semantics change needs. The ADR is a nice touch for documenting the decision.

This PR looks ready for maintainer review. ✅

Generated by Contribution Check · ● 2M ·

@pelikhan
Copy link
Copy Markdown
Collaborator

@copilot add tests to enforce those scenarios

Copilot AI and others added 2 commits April 15, 2026 01:49
…hub:false merge precedence

Agent-Logs-Url: https://github.com/github/gh-aw/sessions/af55a946-53e7-428f-b6ea-25d987f9fba4

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

Copilot AI commented Apr 15, 2026

@copilot add tests to enforce those scenarios

Added tests in e985e79:

Unit tests (pkg/parser/frontmatter_merge_test.go):

  • base bash true overrides additional bash arraybash: true (unrestricted) wins over import's ["ls", "cat"] array
  • base bash array not overridden by additional bash true — reverse direction
  • base cache-memory map with key overrides additional cache-memory true — specific key wins over generic true
  • base cache-memory true not overridden by additional cache-memory map — reverse direction

End-to-end tests (pkg/workflow/importable_tools_test.go):

  • TestBashMainWorkflowOverridesImportBashList — main workflow with bash: true imports a shared workflow with bash: ["ls", "cat"]; compiled output has --allow-all-tools and no shell(ls) / shell(cat) entries
  • TestCacheMemoryMapOverridesBoolInImportChain — parent import with cache-memory: {key: "my-specific-cache-key"} imports a child with cache-memory: true; compiled output uses the specific key, not the default GH_AW_WORKFLOW_ID_SANITIZED pattern

@pelikhan pelikhan merged commit aea3887 into main Apr 15, 2026
@pelikhan pelikhan deleted the copilot/fix-tools-github-override branch April 15, 2026 01:51
Copilot stopped work on behalf of pelikhan due to an error April 15, 2026 01:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: tools.github: false does not override tools.github.* from a shared import

3 participants