fix: copilot host wiring targeted surfaces Copilot CLI does not read - #28
Merged
Conversation
`archcore init --agent copilot` wired a Copilot CLI teammate to two
things that host ignores, so the hook fired into a session with no
archcore tools and no context — silently, with a green install log.
MCP config went to .vscode/mcp.json in VS Code shape ("servers" key,
"type": "stdio"). Copilot CLI dropped that source in v1.0.37
(github/copilot-cli#3019); it belongs to VS Code alone, a surface
copilot-adapter-design.adr explicitly scopes out. The path it moves to
is NOT .github/mcp.json either — GitHub's config-dir reference lists
that one, but it has never been read as a workspace source
(github/copilot-cli#1886, still open; confirmed by the maintainer
closing #1291). The only project-level MCP source Copilot CLI reads is
the workspace-root .mcp.json, keyed "mcpServers", discovered from the
working directory up to the git root.
That is the same file claude-code already writes, with the same key and
the same bare {command, args} entry, so copilot now shares the standard
writer instead of carrying its own. Wiring both hosts converges on one
entry in either order — pinned by a test, since a shared target is new
here and a duplicating merge would reshape the file on every init.
Removing the VS Code writer takes the JSONC corruption policy with it:
corruptSkipInstall existed solely because .vscode/mcp.json may carry
comments. With no JSONC target left, writeMCPConfig drops the policy
parameter and always backs up an unparsable file.
Second fix, same silent class: `archcore hooks copilot session-start`
emitted Claude's {"hookSpecificOutput": {...}} wrapper. We register the
hook under camelCase "sessionStart" — Copilot's native format — whose
documented output is a bare top-level additionalContext. Copilot's
PascalCase compat mode changes only the INPUT payload, never the output
schema, so the wrapper parsed as nothing and the session started blind.
Session-start now takes a per-host output shape; Copilot gets the native
form (no systemMessage slot on that host, so the connected banner is
dropped rather than smuggled into context), every other host keeps the
Claude-compatible one. Both shapes are pinned by tests.
Verified end to end: init writes .mcp.json + .github/hooks/archcore.json
+ AGENTS.md and no .vscode/, and the two hosts' session-start outputs
differ as intended.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`go test ./...` ran in exactly one place: release.yml, on a tag push. So a broken test first surfaced during a release, with the tag already pushed and the only remedy a follow-up tag. Nothing gated a PR — the green checks on one were CodeQL alone, which says nothing about whether the code works. Same three commands the release already trusts, moved to where they can still cheaply say no. Formatting runs first because it is the cheapest signal and the one most likely to differ between a contributor's editor and the repo; gofmt is checked rather than applied so CI never rewrites a contributor's tree. Concurrency cancels superseded runs on branches but not on main, where every commit should keep a result of its own. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
archcore init --agent copilotwired a Copilot CLI teammate to two things that host ignores, so the SessionStart hook fired into a session with no archcore tools and no context — silently, with a green install log.MCP config went to a path Copilot CLI does not read
It wrote
.vscode/mcp.jsonin VS Code shape ("servers"key,"type": "stdio"). Copilot CLI dropped that source in v1.0.37 (github/copilot-cli#3019); it belongs to VS Code alone, a surfacecopilot-adapter-design.adrexplicitly scopes out.The path it moves to is not
.github/mcp.jsoneither. GitHub's config-dir reference lists that one, but it has never been read as a workspace source (#1886, still open; confirmed by the maintainer closing #1291). The only project-level MCP source Copilot CLI reads is the workspace-root.mcp.json, keyedmcpServers, discovered from the working directory up to the git root.That is the same file
claude-codealready writes, with the same key and the same bare{command, args}entry — socopilotnow shares the standard writer instead of carrying its own. Wiring both hosts converges on one entry in either order, pinned by a test since a shared target is new here and a duplicating merge would reshape the file on every init.Removing the VS Code writer takes the JSONC corruption policy with it:
corruptSkipInstallexisted solely because.vscode/mcp.jsonmay carry comments. With no JSONC target left,writeMCPConfigdrops the policy parameter and always backs up an unparsable file.Session-start emitted the wrong output shape
archcore hooks copilot session-startemitted Claude's{"hookSpecificOutput": {...}}wrapper. We register the hook under camelCasesessionStart— Copilot's native format — whose documented output is a bare top-leveladditionalContext. Copilot's PascalCase compat mode changes only the input payload, never the output schema, so the wrapper parsed as nothing and the session started blind.Session-start now takes a per-host output shape. Copilot gets the native form (no
systemMessageslot on that host, so the connected banner is dropped rather than smuggled into context); every other host keeps the Claude-compatible one. Both shapes are pinned by tests.Verification
go vet,gofmt,go test ./...clean. End to end against a scratch repo: init writes.mcp.json+.github/hooks/archcore.json+AGENTS.mdand no.vscode/, and the two hosts' session-start outputs differ as intended.🤖 Generated with Claude Code