Skip to content

[bug] A coder agent can open a GitHub issue and then never touch it — no comment, close, relabel or assign tool, so closing one ticket costs a whole Engine run #507

Description

@serge-ivo

An agent can OPEN an issue and can never touch it again

The GitHub connector has one write tool. A Coder can file a ticket, and then cannot comment on it,
close it, relabel it or assign it — the three things that actually happen to a ticket after it
exists. The owner hit this directly:

Chess coder 2 (bd43f4de-ef35-4051-bdec-43f8571414a1, agent coder-repo), 2026-08-11:

23:02:54 owner — "…what's the current issue, I don't want you to spoil [start] a new issue, but you assign the issue to me, if it needs my input."
23:03:12 agent — "The open issues are noted. On issue #16I can't assign issues to you via my tools (no write access to issue assignment)."

That sentence is true. It is also the second time in two days he has been told a coder agent has
no GitHub write it needs (see #506 for the Lead's filing gap).

What it costs when the agent works around it

Heartfull (f8ddc272-0390-4826-8812-94989e3d2ebd), agent_trace 2026-08-11 21:33:02:

✅ start_work  Started work on: Close GitHub issue #128 in HeartFull-online/platform with the
               comment: "Not in scope — Hearts economy n…

Closing one issue with one sentence of comment consumed a Pilot run and an Engine session on the
owner's machine
— because gh issue close is the only route. The same detour is visible on every
other Repo Coder in the same window, always as an Engine instruction rather than a tool call:

So the capability exists on the platform — it is just only reachable by spending a coding run on it,
which needs a machine running pags up, an engine login, and a repo checkout. The Coder Lead has
none of those
, which is why it cannot do this at all.

Verified — the connector has six tools and one write

workers/api/src/lib/connectors/github.ts, GITHUB_MANIFEST.tools (:154-222), complete:

tool scope line
github_workflow_runs read :160
github_list_issues read :170
github_read_issue read :181
github_list_pulls read :191
github_read_pull read :201
github_create_issue write :211

github_create_issue's params are repo, title, body, labels (:214-219) — no
assignees
, which is precisely the field the owner asked for. And labels is settable only at
creation: nothing can change a label afterwards.

Grepped for the absent names across the whole worker — github_comment, github_close,
github_update_issue, github_assign: 0 hits. There is no handler, no def and no
half-built path.

What to do

One new tool and one extension, both on the existing manifest. No new auth, no new consent
surface, no new scope.
The App installation token already carries issues: write — that is what
makes github_create_issue work today (createIssueHandler, github.ts:131-152, POST /repos/{repo}/issues), and POST …/issues/{n}/comments + PATCH …/issues/{n} use the same
permission. github write consent is already granted per-instance on every Repo Coder
(measured: writeConsent: "granted" on f8ddc272…), so these arrive gated exactly like the
create tool.

1. github_comment_issue{repo, number, body}, scope: "write", POST /repos/{repo}/issues/{number}/comments. This is the one that closes the loop the transcripts show:
recording why something was closed is the whole of steps 14/17 above.

2. github_update_issue{repo, number, state?, labels?, assignees?, title?, body?},
scope: "write", PATCH /repos/{repo}/issues/{number}. Covers close/reopen, relabel and assign in
one call, which is how GitHub models it. assignees answers the quoted denial verbatim.

Both must call invalidateIssuesCache(ctx.env, ctx.userId, repo) the way createIssueHandler:150
does, and for the reason written there — the agent's next github_list_issues would otherwise read
its own pre-write copy and conclude nothing happened.

3. Declare them. A migration in the 0101/0108 shape, appending to the FULL restated
$.capabilities object, one UPDATE per slug: coder-repo (and coder) get both;
coder-lead gets github_comment_issue only — see the open question. capabilitiesForInstance
(lib/agent-capabilities.ts:524-541) JOINs the agents row at read time, so every live instance
picks this up on its next turn with no re-subscribe.

4. Add both to coder2-parity.test.ts's expected declaration so the next whole-object
json_set cannot silently drop them — the trap 0108's header documents and that already fired
once on set_direction.

Alternatives considered and rejected

  • Leave it to the Engine (gh). That is today's behaviour and it is what the evidence indicts:
    a Pilot run + an Engine session to write one comment, unavailable entirely to any agent without a
    runner. It also spends the owner's tokens on a REST call the platform can make itself.
  • One github_issue_action tool with an action enum. Rejected: it hides the write surface
    behind a string, so the write-consent gate and /v1/instances/:id/tools can no longer say what
    the agent may actually do to an issue — the transparency [connectors] Connector consent + write-scope safety + admin visibility #90/[bug] An agent refuses work it IS allowed to do — the prompt says every write tool "needs consent" and never says the consent is granted #399 exist for.
  • A generic github_request passthrough. Rejected: http_request already exists for
    "call any API as configuration" and was deliberately not given the App token; a passthrough on an
    installation token is an unbounded write to every repo the installation covers.
  • Also add PR merge/close. Rejected, and not by me: github_list_pulls's own description says
    "Read-only; there is deliberately no merge tool (the repo's merge policy governs that)"
    (github.ts:203). Issue state is not that decision and does not reopen it.
  • Adding assignees to github_create_issue and stopping there. Rejected: it fixes the quoted
    sentence only for issues the agent opens itself, and the owner's ask was about Trigger payload mapping UI and config validation #16, which already
    existed.

Open question, owner's to decide — should the Lead be able to close an issue?

I would give the Lead github_comment_issue and not github_update_issue.

A Lead that reports on work should be able to leave the record on the ticket ("delegated to FWS,
run 70fc6c23"). Closing is a claim that work is done, and the Lead learns that second-hand from
check_delegation — which is exactly the field it has already been observed over-trusting. Let the
agent that did the work close the ticket. If the owner disagrees, the fix is one extra name in the
same migration.

Acceptance criteria

  • "Assign issue Trigger payload mapping UI and config validation #16 to me and label it high priority" on a Repo Coder does it in ONE tool call,
    with no start_work and no Engine session — verified in agent_trace (a tool.call for
    github_update_issue, no start_work on that turn).
  • "Close Voice: make the "Speaking" indicator tappable to stop TTS #128 with a comment saying it is out of scope" likewise: one comment call, one update
    call, no coding run.
  • Both refuse cleanly with explainWriteConsent's sentence when github write consent is not
    granted for that instance, and never silently no-op.
  • A github_list_issues immediately after either write reflects the change (cache invalidated).
  • tool-reachability.test.ts stays green — i.e. the migration lands in the same PR as the
    connector change, not after it.
  • coder2-parity.test.ts names both tools in the expected declaration.

Regression risk

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingconnectorsConnector + tool framework

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions