[Feat] Resolve bare issue references (#234, ENG-512) against configured repositories#722
Draft
daniel-lxs wants to merge 3 commits into
Draft
[Feat] Resolve bare issue references (#234, ENG-512) against configured repositories#722daniel-lxs wants to merge 3 commits into
daniel-lxs wants to merge 3 commits into
Conversation
parseExternalIssueReferences hardcoded the GitHub and Linear URL shapes and fetchExternalIssueContext hardcoded each provider's MCP call sequence. Both now read from a declarative registry (external-issue-providers.ts): each provider states its hostnames, the pathname pattern that identifies one of its issues, and the ordered MCP fetch attempts a match produces (preserving GitHub's issue_read -> get_issue fallback chain). Adding SCM support becomes one registry entry alongside the MCP server itself and its mcp-policy allowlist. Pure internal refactor: URL extraction, the 2-issue and 8k-char caps, the shared 8s fail-open deadline, the untrusted-context output format, and the model-never-picks-fetch-targets boundary are all unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The routing precheck already extracts references like #234, acme/web#42, or ENG-512 into externalReference, but nothing acted on it when the message carried no pasted URL. When the precheck asks for a lookup and URL parsing finds nothing, the reference now resolves deterministically against the deployment's own configuration: repo-qualified numbers must match a configured repository, bare numbers fan out across configured repositories only when there are at most three (labeled per repo so the informed re-route can disambiguate), and Linear-style identifiers fetch directly. Bare references normalize to canonical URLs and reuse the provider registry, so fetch-attempt construction stays single-source. The model still never picks fetch targets: it proposes reference text, code validates against a closed candidate set and executes under the existing shared 8s fail-open deadline. Pasted URLs take precedence. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Contributor
…xpires A new fake-timer test for the shared 8s deadline exposed that withExternalLookupTimeout received an already-invoked promise, so an exhausted deadline still fired the next MCP call and merely ignored its result (pre-existing since #693). The helper now takes a thunk and checks the remaining budget before dispatching. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Member
Author
|
Added the fake-timer test for the shared 8s deadline — and it caught a real (pre-existing, since #693) bug: |
Base automatically changed from
feature/external-issue-provider-registry
to
develop
July 23, 2026 16:49
daniel-lxs
marked this pull request as draft
July 23, 2026 17:08
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.
What changed
Stacked on #718 (provider registry) — review and merge that first; this PR's base is its branch, so once #718 lands this can be retargeted to
developand will show only the bare-reference diff.Closes the last gap in the lookup gate: the routing precheck already extracts references like
#234,acme/web#42, orENG-512into itsexternalReferencefield, but nothing acted on it when the message had no pasted URL — the flag stayed telemetry-only for bare references. Now, when the precheck returnsneedsExternalLookup=trueand URL parsing finds nothing,gatherExternalIssueContextresolves the reference deterministically:ENG-512-style identifiers → Linearget_issuedirectly.owner/repo#N→ fetched only if that repo is in the deployment's configuredrepositoryNames(case-insensitive match, configured casing wins).#N/N→ fans out across configured repositories, but only when there are at most 3 (else fail open); each result is labeled per repo ([acme/web#234]) so the informed re-route call disambiguates.Bare references normalize to canonical URLs and run through #718's registry, so fetch-attempt construction (incl. GitHub's
issue_read→get_issuefallback) stays single-source. Pasted URLs always take precedence over the precheck reference.Security posture unchanged
The model proposes reference text only; fetch targets come from a closed candidate set (the deployment's own configured repos / the connected Linear workspace), execution stays in server code under the existing shared 8s fail-open deadline. A hallucinated or injection-steered reference can at most read issues the deployment already routes for — the same authority a pasted URL has.
Tests
Five new
external-issue-contextcases (bare Linear id, multi-repo fan-out with per-repo labels, repo-qualified validation incl. unconfigured-repo rejection, too-many-repos fail-open, URL precedence) plus an end-to-endrouter-servicecase (Check issue #234→ fetch →phase: 'mcp'). 13 files / 104 router tests green,pnpm knipclean,tsc --noEmitclean.🤖 Generated with Claude Code