#401 shipped the Pulls panel and, with it, two registry tools so an agent could answer the
question directly: github_list_pulls and github_read_pull (6f6e42f). The panel works. The
tools are unreachable — by every agent on the platform, including the legacy coder.
Ask a Repo Coder "what pull requests are open on this repo?" and it has no tool for it. It will
either decline or reach for the terminal, which is the overlapping drive-path the Coder-2 split
exists to remove.
Correction to the report I was handed
The claim was that "the legacy coder slug declares no allowlist and gets them today." It does
declare one — migration 0054_coder_github_tools.sql re-set coder's full capabilities object,
including "tools":["github_create_issue","github_list_issues","github_read_issue"]. So coder is
on the allowlist path too and is equally blocked. Nothing can call these tools.
Measured, in production
GET /v1/instances/{coder-repo instance}/tools:
{
"name": "github_list_pulls",
"connector": "github",
"scope": "read",
"allowed": false,
"disabled": false,
"reason": "not_declared",
"writeConsent": "n/a"
}
GET /v1/instances/my/instances, resolved capabilities.tools:
| agent |
declared tools |
coder-repo |
repo_tree, repo_read_file, repo_git, repo_remote, github_list_issues, github_read_issue, github_create_issue |
coder-lead |
list_subordinates, subordinate_status, delegate_goal, check_delegation, github_list_issues, github_read_issue |
coder |
github_create_issue, github_list_issues, github_read_issue |
repo-chat |
search_knowledge, list_knowledge, read_knowledge |
Not one carries a pulls tool.
Mechanism
workers/api/src/agent-do-tools.ts:187-203 — a declared list is authoritative, not additive to
a default:
if (declared?.length) {
set = new Set<string>(BASE);
for (const name of declared) if (CREATOR_SELECTABLE_TOOLS.has(name)) set.add(name);
}
A name absent from capabilities.tools is never handed to the model, and a text-embedded call to it
is refused. The registry half is complete and correct — lib/connectors/github.ts:190-209 declares
both with scope: "read" (no write consent needed) and :229-230 wires the handlers — so this is
purely the declaration side.
Two correct decisions composing: an authoritative allowlist is exactly what makes a third-party
creator's tool scope safe (#58), and shipping a tool into the registry is the normal way to add
one. Nothing connects them, so a registry addition is inert until a migration says otherwise.
Do
No console change and no instance migration needed: capabilitiesForInstance
(lib/agent-capabilities.ts:524-541) JOINs the agents row at read time, so updating the agent
reaches every already-subscribed instance on the next turn. Verified.
Alternatives considered
Acceptance
Regression risk
MAX_DECLARED_TOOLS is 40 (lib/agent-capabilities.ts:343); coder-repo goes 7 → 9. No risk,
but it is the cap that silently truncates so it is worth naming.
coder2-parity.test.ts:131-166 parses migration 0070 by regex (declared0070()) to assert
the Repo Coder kept the Co-pilot's read tools. After 0100, 0070 is no longer the effective
declaration and that helper reads a superseded list — it will keep passing while describing the
wrong state. Either point it at the latest migration touching coder-repo, or re-word it to say
it only guards that 0070 did not lose anything. Do not leave it silently stale; that is the
same trap as an expired premise.
- The tools are
scope: "read", so no write-consent surface changes and no new grant is requested
from any owner.
Files: workers/api/src/lib/connectors/github.ts:190-209,229-230,
workers/api/src/agent-do-tools.ts:187-217, workers/api/src/lib/agent-capabilities.ts:343,524-541,
workers/api/migrations/0054_coder_github_tools.sql, workers/api/migrations/0070_coder_repo_one_chat.sql,
workers/api/src/lib/coder2-parity.test.ts:131-166, scripts/check-migrations.mjs.
Related: #401 (which shipped the tools), #58 (why the allowlist is authoritative).
#401shipped the Pulls panel and, with it, two registry tools so an agent could answer thequestion directly:
github_list_pullsandgithub_read_pull(6f6e42f). The panel works. Thetools are unreachable — by every agent on the platform, including the legacy
coder.Ask a Repo Coder "what pull requests are open on this repo?" and it has no tool for it. It will
either decline or reach for the terminal, which is the overlapping drive-path the Coder-2 split
exists to remove.
Correction to the report I was handed
The claim was that "the legacy
coderslug declares no allowlist and gets them today." It doesdeclare one — migration
0054_coder_github_tools.sqlre-setcoder's full capabilities object,including
"tools":["github_create_issue","github_list_issues","github_read_issue"]. Socoderison the allowlist path too and is equally blocked. Nothing can call these tools.
Measured, in production
GET /v1/instances/{coder-repo instance}/tools:{ "name": "github_list_pulls", "connector": "github", "scope": "read", "allowed": false, "disabled": false, "reason": "not_declared", "writeConsent": "n/a" }GET /v1/instances/my/instances, resolvedcapabilities.tools:coder-reporepo_tree, repo_read_file, repo_git, repo_remote, github_list_issues, github_read_issue, github_create_issuecoder-leadlist_subordinates, subordinate_status, delegate_goal, check_delegation, github_list_issues, github_read_issuecodergithub_create_issue, github_list_issues, github_read_issuerepo-chatsearch_knowledge, list_knowledge, read_knowledgeNot one carries a pulls tool.
Mechanism
workers/api/src/agent-do-tools.ts:187-203— a declared list is authoritative, not additive toa default:
A name absent from
capabilities.toolsis never handed to the model, and a text-embedded call to itis refused. The registry half is complete and correct —
lib/connectors/github.ts:190-209declaresboth with
scope: "read"(no write consent needed) and:229-230wires the handlers — so this ispurely the declaration side.
Two correct decisions composing: an authoritative allowlist is exactly what makes a third-party
creator's tool scope safe (#58), and shipping a tool into the registry is the normal way to add
one. Nothing connects them, so a registry addition is inert until a migration says otherwise.
Do
mainis0099_tmux_operator_backend_exclusive_tools.sql. (There are 100 migration files because0092is a historical duplicate pair, recorded as an exception inscripts/check-migrations.mjs;the count is not the highest number.)
0054's shape:json_setre-setting the full$.capabilitiesobject so the JSONstays one source of truth, idempotent, one
UPDATE … WHERE slug = '…'per agent. Do notedit
0054/0070—check-migrations.mjs --require-historyfails a migration whose DDLchanges after the commit that introduced it.
coder-repoandcoder: add both tools. They already read and write issues; pullrequests are the same family and the same auth path.
coder-lead: recommend adding both. A Lead reads issues to decide what to delegate; onethat can see PRs can notice "that is already open in [bug] A negatively-jittered cron run computes its next slot from the early fire time — "@daily" reliably fires twice in one night #412" before spending an Engine on it.
Owner's call — it widens what the Lead reads, and it is the one of the three where the
benefit is an argument rather than a gap.
repo-chat: recommend not adding. Its declared set is deliberately knowledge-only(migration
0050, the first agent built "the creator way") and giving it connector tools is aproduct decision, not a fix for this.
No console change and no instance migration needed:
capabilitiesForInstance(
lib/agent-capabilities.ts:524-541) JOINs the agents row at read time, so updating the agentreaches every already-subscribed instance on the next turn. Verified.
Alternatives considered
connector. Rejected: it would silently widen every existing agent whenever a connector gains a
tool — the exact inversion of what the allowlist is for, and it would have handed
github_workflow_runsto agents that never asked for it.looking at a screen. [feature] Coding agents cannot show a pull request — add a Pulls panel, and make "don't fetch if nothing changed" true with GitHub conditional requests #401's stated gap was the agent's, and the console already proves the data
path works.
Acceptance
GET /v1/instances/:id/toolsfor acoder-repoinstance reportsallowed: trueforgithub_list_pullsandgithub_read_pull.owner/repo?" produces atool.callforgithub_list_pullsinagent_trace— not a decline and not a terminalcommand.
workers/api/src/lib/coder2-parity.test.tsalready uses for the issue tools.Regression risk
MAX_DECLARED_TOOLSis 40 (lib/agent-capabilities.ts:343);coder-repogoes 7 → 9. No risk,but it is the cap that silently truncates so it is worth naming.
coder2-parity.test.ts:131-166parses migration0070by regex (declared0070()) to assertthe Repo Coder kept the Co-pilot's read tools. After
0100,0070is no longer the effectivedeclaration and that helper reads a superseded list — it will keep passing while describing the
wrong state. Either point it at the latest migration touching
coder-repo, or re-word it to sayit only guards that
0070did not lose anything. Do not leave it silently stale; that is thesame trap as an expired premise.
scope: "read", so no write-consent surface changes and no new grant is requestedfrom any owner.
Files:
workers/api/src/lib/connectors/github.ts:190-209,229-230,workers/api/src/agent-do-tools.ts:187-217,workers/api/src/lib/agent-capabilities.ts:343,524-541,workers/api/migrations/0054_coder_github_tools.sql,workers/api/migrations/0070_coder_repo_one_chat.sql,workers/api/src/lib/coder2-parity.test.ts:131-166,scripts/check-migrations.mjs.Related: #401 (which shipped the tools), #58 (why the allowlist is authoritative).