516 Commits

Author SHA1 Message Date
Sam Morrow 24dc8b08c1 fix(copilot): keep rate limit denials out of the review permission hint
A 403 carrying X-RateLimit-Remaining: 0, or a secondary rate limit
documentation URL, reached copilotReviewErrMsg as a rate limit error. It
was explained as a missing repository or missing write access, and the
repository read it triggered was refused for the same reason, so the
caller paid an extra call to be told the wrong thing.

Return the base message for both rate limit error types so the rate
limit text stands on its own, and trim the helper and its tests to the
comments the code cannot state.

Co-authored-by: Dylan Pulver <dylanpulver@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-08-19 16:59:02 +02:00
Dylan Pulver e95b7fda29 fix(copilot): explain review request denials instead of forwarding a bare 404
The review request endpoint requires write access to the repository, and
GitHub refuses a caller without it with 404 Not Found rather than a
permission error. Authoring the pull request does not grant that access,
so a fork contributor can be offered a Copilot review by the web UI and
still be refused by request_copilot_review, with nothing in the tool
result to say why.

On 403 or 404 the tool now reads the repository once so it can name the
cause. A caller without write access is told so directly and pointed at
the web UI. When the repository cannot be read at all, or when write
access is present, the message says so and points at the likelier
cause.
2026-08-19 16:59:02 +02:00
Sam Morrow 0bb1e569ce test(sanitize): drop the optimization scaffolding
The benchmarks and the reference-implementation equivalence harness
existed to justify the sanitizer rewrite. They have served that purpose,
so remove them along with the verbatim copy of the old pipeline they
carried.

Five checks move into sanitize_test.go rather than going away, because
none of them reference the old implementation and all of them guard
behaviour the rewrite introduced:

- isHTMLInert must be a fixed point of the live bluemonday policy,
  checked byte by byte and as whole strings, with the accepted byte set
  pinned explicitly. Nothing else fails if that set is widened, and
  widening it changes sanitizer output.
- Both filters are fixed points on their own output, which is what
  licenses Sanitize to skip its second pass.
- Clean ASCII sanitizes with zero allocations.
- Invalid UTF-8 is re-encoded to U+FFFD.
- Known payloads still lose content.

Net -560 lines.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-08-19 16:42:46 +02:00
Sam Morrow 02cf0f7d75 perf(sanitize): make clean text allocation-free on the hot path
Sanitizing user-authored response fields ran multiple allocating passes
over every string regardless of content: FilterInvisibleCharacters
converted the whole input to []rune and back, FilterCodeFenceMetadata
split and rejoined every line, and bluemonday ran unconditionally. On
comment- and issue-heavy responses this dominated conversion CPU and
allocation.

Three changes, none of which alter output or widen what the policy
allows:

- FilterInvisibleCharacters scans first and copies only from the first
  filtered rune, skipping ASCII runs without decoding them. Invalid
  UTF-8 is still re-encoded to U+FFFD, matching the []rune round trip it
  replaces.
- FilterCodeFenceMetadata walks lines in place and returns the input
  when no line changes.
- FilterHTMLTags skips bluemonday for input that is provably a fixed
  point of the policy: printable ASCII, TAB and LF, with none of the
  five characters html.EscapeString rewrites. Sanitize also skips the
  second invisible/code-fence pass when HTML normalization returned its
  input unchanged, since both filters are fixed points there.

Equivalence is pinned by a verbatim copy of the previous pipeline: the
new code is diffed against it over a corpus of ~22k deterministic cases
plus two fuzz targets, and the fast path is checked byte by byte against
the live bluemonday policy.

Benchmarks (Intel Ultra 9 185H, n=6):

  Sanitize/TitleASCII      5.35µs ->  114ns   1 -100% allocs
  Sanitize/Comment1KiB     45.3µs -> 1.27µs   1 -100% allocs
  Sanitize/Body64KiB       2.47ms -> 85.9µs   1 -100% allocs
  30 issues x 2KiB body    3.00ms -> 88.6µs   1.55MiB -> 1.9KiB
  100 comments x 1KiB      5.04ms ->  169µs   2.19MiB -> 6.3KiB

Content that genuinely needs rewriting still pays for it, and non-ASCII
text still goes through bluemonday by design.

Fixes #3117

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-08-19 16:33:23 +02:00
Sam Morrow acd2afc766 refactor(lockdown): trim comments to non-obvious invariants
The cache changes carried explanatory comments that restated the code or
narrated what each step did. Drop them and keep only what the code cannot
express: that cache2go never reclaims a named table, that its own expiry
slides on every read, that createdAt survives entry updates, and that
RepoAccessOpts is shared across requests. Exported options keep a short
doc comment.

Comment-only; no behavior change.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-08-19 16:30:34 +02:00
Sam Morrow 56c9088c60 fix(lockdown): scope repo-access cache per identity via entry keys
Isolating identities by deriving a cache2go table name per token grew a
process-wide registry that is never reclaimed: cache2go creates each named
table on first use and never evicts it, so every distinct bearer token —
including invalid ones, since the table was built before GitHub validated
the token — permanently added a table.

Keep a single cache table and scope entries instead. WithIdentity stores a
SHA-256 digest of the identity and prefixes each entry key with it, so
different identities still cannot observe each other's trust decisions,
while per-identity state is reclaimed by the table's ordinary TTL cleanup.
WithCacheName stays for tenant/test isolation, with docs warning against
deriving names from request data.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-08-19 16:30:34 +02:00
Sam Morrow a429a8759f fix(lockdown): bound repo-access cache expiry and isolate it per identity
The repo-access cache used by lockdown mode relied on cache2go's sliding
expiry: every read extends an entry's life, so a frequently-accessed
entry could keep a stale trust decision (e.g. revoked push access)
alive indefinitely instead of refreshing after its TTL.

Separately, cache2go.Cache(name) returns a process-wide singleton table
keyed by name. In HTTP mode, RequestDeps.GetRepoAccessCache built a new
RepoAccessCache per request but always reused the same default-named
table, so trust decisions computed under one caller's credentials could
be served to a different caller for the same owner/repo, without ever
validating the second caller's own access.

Fixes:
- Track each cache entry's original creation time and bound its maximum
  age from that fixed point, not from last access, so entries are
  refreshed after a fixed TTL regardless of read frequency.
- Add lockdown.CacheNameForIdentity, which derives a stable, hashed
  cache-table name from a request identity (e.g. auth token). Two calls
  for the same identity return the same name (reusing a warm cache
  across a session's repeated requests); different identities always
  get different names (no shared cache state).
- RequestDeps.GetRepoAccessCache now scopes each request's cache to the
  requesting token's identity via CacheNameForIdentity, closing the
  cross-identity leak in HTTP/multi-tenant deployments. Stdio mode is
  unaffected: it constructs a single RepoAccessCache for the whole
  process lifetime, as before.

Tests added:
- TestRepoAccessCacheBoundedExpiryIgnoresRepeatedAccess and
  TestRepoAccessCacheNewUserDoesNotResetEntryAge exercise bounded expiry
  deterministically via an injectable clock (no sleeps).
- TestCacheNameForIdentity and
  TestRepoAccessCacheIdentityScopedNamesPreventCrossIdentityLeakage
  cover the naming helper and cross-identity isolation at the lockdown
  package level.
- TestGetRepoAccessCacheIsolatesTrustDecisionsPerIdentity in
  pkg/github mirrors the HTTP server's exact construction pattern
  end-to-end and fails without the dependencies.go fix.

Fixes #3107

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-08-19 16:30:34 +02:00
Sam Morrow 08edfa86f3 refactor: condense lockdown comments in pull_request_read get_commits
Trim the GetPullRequestCommits doc comment to a single terse sentence
and remove a test comment that only restated the test name.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-08-19 15:28:10 +02:00
Sam Morrow 912cce687b Sanitize remaining issue-ref and blame headline response paths
Route every MinimalIssueRef/MinimalPullRequestRef construction through shared
constructors that sanitize the user-authored title, so issue_dependency_read,
issue_dependency_write and find_duplicate no longer forward raw issue titles.

Also sanitize the get_file_blame commit message headline, after truncation so
the headline is still cut at the author's real first line break.

Extends the sanitization regression suite with the project status update body,
both ref constructors and the dependency ref, and adds tool-level regression
tests for find_duplicate and get_file_blame.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-08-19 15:20:33 +02:00
Sam Morrow 56bfeec0a9 Centralize sanitization of untrusted GitHub response fields
Sanitization was previously applied ad hoc at a handful of tool call
sites (GetIssue, GetPullRequest, ListPullRequests) rather than in the
shared convertToMinimal* converters, so equivalent user-authored text
returned by other tools (issue comments, PR reviews, review comments,
releases, commit messages, discussions, project item titles) was
returned unsanitized.

- Apply sanitize.Sanitize inside the convertToMinimal* helpers in
  minimal_types.go for issue/PR titles and bodies, issue comments, PR
  reviews, review comments, releases, commit messages, and project
  item content titles. This is the single, shared conversion point
  used by nearly every read tool, so fixing it there covers get/list
  issues, pull requests, comments, reviews, review comments, releases,
  commits, and project items consistently.
- Add a sanitizeIssueTitleAndBody helper and use it for the two
  response paths that marshal a raw *github.Issue directly instead of
  a Minimal* type: search_issues (SearchIssueResult.MarshalJSON) and
  search_pull_requests (searchHandler).
- Sanitize discussion titles/bodies/comments (list_discussions,
  get_discussion, get_discussion_comments), which previously had no
  sanitization at all, via a new newMinimalDiscussionComment
  constructor and inline fixes.
- Sanitize project status update bodies.
- Remove the now-redundant scattered sanitize calls in GetIssue,
  GetPullRequest, and ListPullRequests now that the shared converters
  sanitize on their own.

Patches, diffs, and raw file contents are intentionally left
untouched to preserve fidelity.

Adds table-driven regression tests covering every touched converter,
the search_issues/search_pull_requests raw-passthrough paths, and a
fidelity check that patches/diffs are not altered.

Fixes #3106

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-08-19 15:20:33 +02:00
Sam Morrow 3bad3bc651 fix(http): make server lockdown mode an upper bound over requests (#3112) 2026-08-19 15:20:19 +02:00
Sam Morrow 769340d6a1 fix(lockdown): harden pull_request_read get_commits handling
pull_request_read's get_commits method previously returned commit
messages without any lockdown check, unlike get_diff and get_files
which restrict the whole result when the PR author lacks push access.
Commit content is part of the same untrusted head branch as the diff
and file list, so GetPullRequestCommits now reuses
enforcePullRequestLockdown for consistent, fail-closed behavior
without adding a per-commit permission lookup.

Also updates the lockdown documentation in README.md and
docs/server-configuration.md to:
- list pull_request_read:get_diff, get_files, and get_commits among
  the tools that error when the PR author lacks push access (get_diff
  and get_files were already implemented this way but undocumented)
- clarify that lockdown mode is a best-effort content filter to
  reduce prompt-injection risk, not an authorization boundary
- document the existing intentional trusted-bot exception
  (github-actions[bot], copilot) accurately

Fixes #3105

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-08-19 14:41:45 +02:00
Sam Morrow c64b6fed32 fix(issues): narrow search field enrichment fallback
Only tolerate GraphQL schema validation failures that show the optional Issue.issueFieldValues selection or its known fragments are unsupported. Surface client, auth, rate-limit, network, resolver, malformed response, and unrelated GraphQL failures.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-08-19 13:09:34 +02:00
kerobbi ee37dd8bdb make search_issues field value enrichment best-effort 2026-08-19 13:09:34 +02:00
Sam Morrow 1e7b3f9bc5 fix(issues): preserve delete field semantics
Clarify that delete:false is ignored, retain mutual exclusion for delete:true, and reject invalid delete types. Add focused schema and handler regressions.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-08-19 13:08:25 +02:00
Travis Gockel 9cfe97e20a fix(issues): allow delete:false in issue_write issue_fields
The `delete` property of issue_write's issue_fields items was declared with
Enum: []any{true}, making true its only legal value. The property is optional,
but a client that fills every property of a schema -- common, since
OpenAI-style strict function calling requires every property to appear in
`required` -- had no way to express "not deleting this field": there is no
false in the enum and no null in the type. `value` offers no alternative
either, being typed ["string","number","boolean"] with no null.

The MCP Go SDK validates arguments against the resolved input schema before
the handler runs, so delete: false was rejected at schema validation and never
reached optionalIssueWriteFields. Such clients sent delete: true alongside a
value instead and hit the handler's mutual-exclusion check, so issue_write
could never set an issue field for them.

Remove the enum so false is a legal no-op, and document that omitting the
property or setting it to false leaves the field unchanged. No handler change
is needed: the code already branches on `if deleteField`, so false falls
through to the normal value path, and the mutual-exclusion check for
delete: true still applies. Add tests for optionalIssueWriteFields, which had
none.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 13:08:25 +02:00
Sam Morrow a6b820741b fix(repos): label deferred symlink content accurately
Report contentless large symlink targets as not returned while preserving their ResourceLink and requested-path identity.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 21420b11-5dae-49b6-ac77-965faec7f88b
2026-08-19 13:06:59 +02:00
Sam Morrow f8d38356e9 refactor(repos): simplify symlink read disclosure
Keep the lazy blob-identity check and bounded tree fallback while consolidating metadata handling and request-count tests.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 21420b11-5dae-49b6-ac77-965faec7f88b
2026-08-19 13:06:59 +02:00
Sam Morrow 8aeb670b0b fix(repos): disclose dereferenced symlink reads
Detect internal symlink dereferences from Git blob identity mismatches, disclose explicit links and submodules, and preserve requested-path resource output. Use bounded exact-path tree inspection only when inline content is unavailable.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-08-19 13:06:59 +02:00
Sam Morrow 30600ba335 perf(repos): reduce symlink guard overhead
Remove persistent repository and push-files guidance in favor of concise runtime recovery content. Resolve refs directly through Git Trees, skip inspection for explicit symlinks and opt-ins, and lock request counts in tests.\n\nCo-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-08-19 13:06:59 +02:00
Sam Morrow bb3a1b2a03 fix(repos): guard writes to symbolic links
Detect existing symlinks through the Git tree and require an explicit opt-in before changing their targets. Return the resolved repository target so callers can safely update the linked file instead.

Refs #2997

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-08-19 13:06:59 +02:00
Keshav Malik 596203ef54 Clarify symlink behavior for repository file writes 2026-08-19 13:06:59 +02:00
Sam Morrow 46651854ef fix(tools): allow omitted tool arguments
Normalize missing or zero-length tool arguments to an empty object while preserving invalid JSON and required-parameter validation.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-08-19 12:24:04 +02:00
Sam Morrow 316b8efcd0 test(issues): cover GHES fallback assignees
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-08-19 11:52:33 +02:00
Sam Morrow 91de9a0bf1 fix(issues): harden GHES schema fallback
Handle alternate issue-field validation messages, preserve primary and retry errors, and avoid runtime result type switches.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-08-19 11:52:33 +02:00
Sam Morrow b036edcac5 fix(issues): fall back on unsupported field schemas
Retry list_issues without custom issue field dependencies only when the
host schema lacks them. Preserve explicit field filters and propagate
unrelated GraphQL errors.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-08-19 11:52:33 +02:00
Sam Morrow 8395beae41 test(issues): cover stable assignee responses
Verify issue_read returns assigned logins and a definitive empty array for unassigned issues. Exercise the same empty-array contract through list_issues field filtering.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-08-19 11:36:21 +02:00
Travis Gockel 95a8e75705 Return assignees from list_issues
The list_issues GraphQL fragment never selected assignees, so the tool
could not report who an issue was assigned to. Its nearest field, user,
is the issue author, which callers conflate with the assignee. Answering
"is anything unassigned?" therefore cost one list_issues call plus one
issue_read per candidate, and a truncated sweep invites a fabricated
answer drawn from the author instead.

Add an assignees selection to IssueFragment, flatten it to logins in
fragmentToMinimalIssue, and add "assignees" to listIssuesItemFieldEnum so
it is selectable through fields. GitHub caps issue assignees at 10, so
first: 100 cannot truncate; it also matches the page size already used
for assignees in copilot.go.

Drop omitempty from MinimalIssue.Assignees and initialize the slice in
both converters so an unassigned issue serializes as [] rather than an
absent key, which is what lets a caller identify unassigned issues from
a single response. This also affects issue_read, the other MinimalIssue
consumer, which now reports "assignees": [] instead of omitting the key.
2026-08-19 11:36:21 +02:00
Sam Morrow 3000061430 fix(repositories): return raw bytes for MCP blobs
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-08-19 11:32:08 +02:00
Sam Morrow d8a1627b13 fix(notifications): mark subscription tools destructive
Both tools expose a delete action, so the tool-level annotation must remain
conservative even though their other actions only update preferences.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: be9c15fe-a113-4ebd-b9ab-09a520d522b6
2026-08-19 11:31:01 +02:00
Sam Morrow bf47e3eca9 fix(errors): safely format GitHub validation failures
Limit structured formatting to HTTP 422 responses, sanitize allowlisted validation fields, and omit request, response, and documentation metadata while preserving other error contracts.

Refs #3080

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-08-19 00:04:54 +02:00
Hashim1999164 4e6329eb46 Show nested GitHub API validation messages in tool errors
create_branch currently forwards the compact 422 dump, which hides
ruleset details the GitHub UI already shows. Unwrap ErrorResponse so agents
can see each validation message and recover.
2026-08-19 00:04:54 +02:00
Sam Morrow 21c5a6f1dd fix(auth): scope tokens across GitHub clients
Use exact configured host authorities for every REST, GraphQL, and raw client so redirects cannot reattach credentials to foreign hosts or ports. Add adversarial redirect and lookalike coverage.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-08-19 00:02:40 +02:00
Syed Anas Mohiuddin 198017fb54 Attach GitHub token only to configured GitHub hosts
BearerAuthTransport re-adds the Authorization header on every hop, which
defeats net/http's cross-host redirect stripping. Scope the credential to
the configured hosts so a redirect off them travels without the token.

An empty AllowedHosts preserves prior behavior; the three production
construction sites populate it from the configured REST, upload, GraphQL
and raw hosts.
2026-08-19 00:02:40 +02:00
Sam Morrow 2211a4d645 fix(issues): validate issue comment input modes
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-08-18 23:42:29 +02:00
Sam Morrow 8ec62491c6 Add confirmed repository deletion tool (#3076)
* feat(repos): add confirmed repository deletion

Add a destructive delete_repository tool that requires an exact owner/repo confirmation through multi-round-trip elicitation. Gate the tool to MCP protocol 2026-07-28 and newer across local and remote transports.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 4b04480c-c2e9-483e-9b0f-34830b76a2f8

* refactor(inventory): generalize tool availability guards

Gate protocol-restricted tools on required elicitation capabilities and enforce direct calls inside the registered handler so SDK result finalization remains intact.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 4b04480c-c2e9-483e-9b0f-34830b76a2f8

* feat(http): protect MRTR request state

Seal repository deletion targets for self-hosted HTTP with a stable AES-256-GCM key. Hide only delete_repository when no key is configured and expose an optional sealer interface for remote integrators.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 4b04480c-c2e9-483e-9b0f-34830b76a2f8

* fix(repos): expire deletion confirmations

Bind sealed repository deletion state to the immutable repository ID and a ten-minute expiry. Re-check identity before deletion so replay cannot affect a recreated repository.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 4b04480c-c2e9-483e-9b0f-34830b76a2f8

* fix(http): preserve tool and scope restrictions

Apply static allowlists before removing unavailable tools and fail closed on invalid configured tool names. Model independent OAuth requirements as conjunctive groups so repository deletion requires both delete_repo and repo.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 4b04480c-c2e9-483e-9b0f-34830b76a2f8

* fix(repos): require protected confirmation state

Give stdio a process-local request-state sealer and make deletion fail closed without one. Preserve legacy any-of OAuth behavior globally while documenting and enforcing delete_repository's conjunctive delete_repo and repo requirements.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 4b04480c-c2e9-483e-9b0f-34830b76a2f8

* fix(oauth): request repository deletion scope

Include delete_repo in the supported OAuth scope set used by stdio login, HTTP protected-resource metadata, and tool filtering.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 4b04480c-c2e9-483e-9b0f-34830b76a2f8

* fix(oauth): require deletion scope opt-in

Keep delete_repo in protected-resource discovery for step-up authorization while excluding it from the default stdio OAuth grant.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 4b04480c-c2e9-483e-9b0f-34830b76a2f8

* refactor(oauth): derive scope sets from catalog

Generate protected-resource supported scopes and the lower-risk default OAuth grant from one canonical scope definition list.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 4b04480c-c2e9-483e-9b0f-34830b76a2f8

* refactor(scopes): own OAuth scope catalog

Move supported and default OAuth scope policy into pkg/scopes so protected-resource metadata and stdio grants derive from the scope domain package.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 4b04480c-c2e9-483e-9b0f-34830b76a2f8

* fix(scopes): require workflow scope opt-in

Keep workflow and codespace in protected-resource discovery while excluding both from the default OAuth grant alongside delete_repo.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 4b04480c-c2e9-483e-9b0f-34830b76a2f8

---------

Copilot-Session: 4b04480c-c2e9-483e-9b0f-34830b76a2f8
2026-08-18 14:50:38 +02:00
Sam Morrow accc2e0970 fix(actions): avoid malformed response on log download failure
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 9cdeefb9-91cd-4f65-8eb2-089c9e00a2b8
2026-08-14 10:53:39 +02:00
Bryan Zwicker 2198e8599b Add visible fields to project views (#2988)
* Add visible fields to project views

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 1421a5d5-fdce-4c0e-9528-56d555ec30d4

* Fail fast and surface orphaned views on project view writes

Reject roadmap layouts before enumerating project fields in both the
create and update paths, and verify view ownership before resolving
visible fields on update, so rejected requests no longer pay for a
paginated field listing.

Skip the follow-up filter mutation when the filter is explicitly null,
since a new view has no filter to clear, and include the created view ID
when cleanup after a failed filter mutation also fails so the caller can
recover the orphaned view.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1421a5d5-fdce-4c0e-9528-56d555ec30d4
2026-08-12 09:40:51 -04:00
Bryan Zwicker d6cab9757a Add basic project view management (#2961)
* Add basic project view management

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: c6f8ede6-efee-4191-900d-59a1bb0af000

* Harden project view mutations

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: c6f8ede6-efee-4191-900d-59a1bb0af000

* Resolve project view fields by name

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: c6f8ede6-efee-4191-900d-59a1bb0af000

* Clear project view filters with explicit null

Align the filter parameter with the nullable-parameter convention: omit
to preserve, pass null to clear. Empty strings are now rejected rather
than treated as a clear sentinel. The GraphQL and REST wire format is
unchanged, since the API still clears a filter with an empty string.

Also replace the "<nil>" string comparison in deleteProjectView with a
direct nil check on the returned ID.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Use caller-specific project field hints

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: c6f8ede6-efee-4191-900d-59a1bb0af000

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: c6f8ede6-efee-4191-900d-59a1bb0af000
2026-08-12 08:41:33 -04:00
Tommaso Moro ff15f6825d Use minimal types for tool responses (#3055)
Return compact response shapes for pull request statuses, review comment replies, and individual workflow runs.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: d6786153-698a-4563-97ad-a8221c40e306
2026-08-12 12:33:51 +01:00
Tommaso Moro eff4c3c041 Minimize Actions workflow list responses (#3047)
Return compact response types for workflow run and workflow job lists while retaining diagnostic, step, and runner metadata.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 0eecbca7-7271-4a04-8d28-d952c27ed9c1
2026-08-10 15:13:30 +01:00
Iulia Bejan cdfa34e0a9 Order list_label results by issue count (descending) (#2974)
* Order list_label results by issue count (descending)

Sends orderBy: {field: ISSUE_COUNT, direction: DESC} on the GraphQL
labels query so the most-used labels (by issue count) are returned
first. ISSUE_COUNT is accepted by the GitHub GraphQL API but is not
part of the public schema docs or the githubv4 client library's
LabelOrderField constants, so it is defined locally.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Potential fix for pull request finding

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

* regen docs

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-08-10 10:35:52 +02:00
Bryan Zwicker eb4c099e05 Support singular Project Issue Field updates (#2941)
* Implement batch project write engine

Resolve and validate shared field updates and item references before executing ordered, chunked GraphQL writes with explicit ambiguous outcomes.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 7ae767ff-c1d0-46a9-b126-2e91403993a0

* Expose update_project_items

Add the public projects_write contract, routing, handler coverage, and generated documentation for shared field updates across batches of up to 50 items.

Co-authored-by: Lizeth Vera <47796851+veralizeth@users.noreply.github.com>

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 7ae767ff-c1d0-46a9-b126-2e91403993a0

* Classify batch resolution failures

Use a neutral code for non-structured lookup failures while preserving structured resolution details.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 7ae767ff-c1d0-46a9-b126-2e91403993a0

* Resolve issue references concurrently

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 7ae767ff-c1d0-46a9-b126-2e91403993a0
Copilot-Session: 5709a470-df75-43ec-9a9c-98868e6065d2

* Add singular Issue Field project updates

Support name-based attached Issue Field updates for singular Project items while preserving existing read and standard field behavior.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 4c94f3ce-c04a-482f-830b-ab85abc3f6e4

* Preserve iteration project field updates

Bypass Issue Field metadata resolution for standard field data types and recognize exact missing fragment-type schema errors.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 4c94f3ce-c04a-482f-830b-ab85abc3f6e4

* Adding GraphQL-Features: update_issue_suggestions

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Lizeth Vera <47796851+veralizeth@users.noreply.github.com>
Copilot-Session: 7ae767ff-c1d0-46a9-b126-2e91403993a0
Copilot-Session: 5709a470-df75-43ec-9a9c-98868e6065d2
Copilot-Session: 4c94f3ce-c04a-482f-830b-ab85abc3f6e4
2026-08-07 08:31:43 -04:00
Michael Jacholke 1b3f89a90a Add non-default find_duplicate tool gated by duplicate_detection flag (#3020)
* Add non-default find_duplicate tool gated by duplicate_detection flag

* Trim find_duplicate output to spec fields and relax confidence_threshold bounds

* Attach repo-visibility IFC label to find_duplicate results
2026-08-07 09:45:49 +01:00
Bryan Zwicker e7f7bb8b31 Support removing issue types (#2999)
* Render union types in generated docs

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: ea8faa5c-7f26-4e2d-bf9c-6f0b5f173e8c

* Support clearing issue types with issue_write

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: ea8faa5c-7f26-4e2d-bf9c-6f0b5f173e8c

* Support clearing issue types with granular tool

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: ea8faa5c-7f26-4e2d-bf9c-6f0b5f173e8c

* Validate duplicate closures before updates

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: ea8faa5c-7f26-4e2d-bf9c-6f0b5f173e8c

---------

Copilot-Session: ea8faa5c-7f26-4e2d-bf9c-6f0b5f173e8c
2026-08-06 20:25:58 -04:00
Kelsey Myers f3cb662c25 Make search_issues semantic by default (#2964)
* Make search_issues semantic by default

* initialize description depending on the host

---------

Co-authored-by: Iulia B <iulia-b@github.com>
Co-authored-by: Iulia Bejan <64602043+iulia-b@users.noreply.github.com>
2026-08-06 16:51:13 +02:00
Bryan Zwicker e6e3a4e841 Return closing pull requests from issue_read (#3006)
* Return closing pull requests from issue_read

Answering "is there a PR that closes this issue?" previously required
listing pull requests and grepping their bodies for closing keywords,
which is expensive and unreliable. GraphQL already exposes
Issue.closedByPullRequestsReferences.

Add it to the existing issue_read `get` enrichment query so the answer
comes back in the same round-trip as the hierarchy signals, as a compact
`closed_by_pull_requests` list. An enriched issue with no closing pull
requests serializes an explicit empty list so an agent can stop looking.

Lockdown mode filters references whose author cannot be verified as safe
content, mirroring the existing parent reference handling.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a0b58914-0d94-47a9-8229-c0ef7e32e69f

* Cap embedded closing pull requests and report the total

This enrichment runs on every issue_read get, so embedding up to 25
references costs more than the common case is worth. Embed at most 5,
keeping orderByState so open pull requests are the ones that survive.

Select totalCount alongside the nodes and return the summary as an
object of total_count plus references, so the rare issue with more than
five linked pull requests cannot be read as a complete list. The common
zero-to-two case stays compact and an empty result stays definitive.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a0b58914-0d94-47a9-8229-c0ef7e32e69f

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: a0b58914-0d94-47a9-8229-c0ef7e32e69f
2026-08-06 09:04:10 +01:00
eric sciple 3778a41476 Clarify that create_or_update_file content is plain text (#2983)
The content parameter is passed to the API as plain text and the server
base64-encodes it, but the description said only "Content of the file".
The REST endpoint this wraps documents its own content field as base64,
so a model reading the tool description has a strong reason to encode the
content itself. When it does, the server encodes again and the file is
committed containing base64 text. Every layer reports success.

Describe the value by how it should end up on disk rather than by what
not to do, so a file whose contents are legitimately base64 is still
unambiguous, and name the encoding step so the conflict with the REST
API docs is resolved rather than merely overridden.
2026-07-31 08:49:14 +01:00
Sam Morrow ca8ab52dcc test: assert advertised capabilities omit list-changed
Documentation Check / docs-check (push) Has been cancelled
golangci-lint / lint (push) Has been cancelled
GoReleaser Release / release (push) Has been cancelled
Publish to MCP Registry / publish (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
MCP Server Diff / mcp-diff (push) Has been cancelled
MCP Server Diff / mcp-diff-http (push) Has been cancelled
Build and Test Go Project / build (macos-latest) (push) Has been cancelled
Build and Test Go Project / build (ubuntu-latest) (push) Has been cancelled
Build and Test Go Project / build (windows-latest) (push) Has been cancelled
Docker / build (push) Has been cancelled
Add a regression test locking in the capability contract set by NewMCPServer:
tools, prompts, and resources are advertised without list-changed
notifications, the deprecated logging capability is not advertised, and the
inferred completions capability is preserved. Covers both the stdio path (full
inventory, items present) and the HTTP path (inventory emptied for the
discovery request), which share the same NewMCPServer entry point.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 95b8432c-f280-472e-a242-d3ca6dc31f19
2026-07-30 15:22:06 +02:00
Sam Morrow ea8099d7b2 fix: don't advertise unsupported list-changed capabilities
The server exposes a static set of tools, prompts, and resources and never
mutates them at runtime, so it never emits list_changed notifications. When
capabilities are left unset, the go-sdk infers listChanged:true from the
presence of items and advertises tools/prompts/resources list-change support
we don't actually provide - and the 2026-07-28 spec (subscriptions/listen)
tightens expectations around this.

Declare empty tools/prompts/resources capabilities in NewMCPServer so both the
stdio and remote servers advertise honestly. The remote HTTP handler already
set these explicitly; that duplication is now removed in favour of the shared
default, leaving only the remote-specific schema cache.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 95b8432c-f280-472e-a242-d3ca6dc31f19
2026-07-30 15:22:06 +02:00