Public: anyone can read this; the crew can change it.

Reading …/issues/{number} with a pull's number answers with the pull, marked kind "pull", not not_found #3313

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

`GET /api/v1/repos/:owner/:name/issues/:number` now answers a pull's number with the same issue-shaped body (labels and comments included), where it used to answer `not_found`. The body has `kind: "pull"` and a new `pull` field holding `/api/v1/repos/{owner}/{name}/pulls/{number}`, where the diff and status live. The new tests and the neighbouring issues, OpenAPI and API-error suites pass (21 test…

shipped production is serving it deploy b4d876b

opened this

GET /api/v1/repos/:owner/:name/issues/:number now answers a pull's number with the same issue-shaped body (labels and comments included), where it used to answer not_found. The body has kind: "pull" and a new pull field holding /api/v1/repos/{owner}/{name}/pulls/{number}, where the diff and status live. The new tests and the neighbouring issues, OpenAPI and API-error suites pass (21 tests). I couldn't run the type check because TypeScript isn't installed in this sandbox, so the forge's check will be its first run.

  • Route: the handler in src/http/api.ts now loads the shared issue-or-pull row (loadConversation) instead of the issues-only loader.
  • Unchanged: a plain issue answers as before, with no pull field. A pull in a repo the caller can't read is still not_found, because the same visibility check applies.
  • Tests: test/issue-route-pull.test.ts covers three cases: a pull read through the issues route, an issue still having no pull field, and a pull in a private repo returning 404. The first would fail without the change.
  • Risk check: I did not search src/ and the plugins for clients that relied on not_found to mean "is a pull". Do that before merging.
  • Left out: the other issue sub-routes (comments, dependencies, labels, teams) still treat a pull's number as not_found. I also did not document the new field in OpenAPI or the docs, since no spec mentions the route.

To try it: curl $FORGE_URL/api/v1/repos/trevato/forge/issues/<a pull's number> should return kind: "pull" and the pull path.

🤖 Generated with Claude Code

Closes #3299

commented

GET /api/v1/repos/:owner/:name/issues/:number now answers a pull's number with the same issue-shaped body (labels and comments included), where it used to answer not_found. The body has kind: "pull" and a new pull field holding /api/v1/repos/{owner}/{name}/pulls/{number}, where the diff and status live. The new tests and the neighbouring issues, OpenAPI and API-error suites pass (21 tests). I couldn't run the type check because TypeScript isn't installed in this sandbox, so the forge's check will be its first run.

  • Route: the handler in src/http/api.ts now loads the shared issue-or-pull row (loadConversation) instead of the issues-only loader.
  • Unchanged: a plain issue answers as before, with no pull field. A pull in a repo the caller can't read is still not_found, because the same visibility check applies.
  • Tests: test/issue-route-pull.test.ts covers three cases: a pull read through the issues route, an issue still having no pull field, and a pull in a private repo returning 404. The first would fail without the change.
  • Risk check: I did not search src/ and the plugins for clients that relied on not_found to mean "is a pull". Do that before merging.
  • Left out: the other issue sub-routes (comments, dependencies, labels, teams) still treat a pull's number as not_found. I also did not document the new field in OpenAPI or the docs, since no spec mentions the route.

To try it: curl $FORGE_URL/api/v1/repos/trevato/forge/issues/<a pull's number> should return kind: "pull" and the pull path.

🤖 Generated with Claude Code

Approved

Clean, and it does what #3299 asks.

  • Security: the route now goes through loadConversation, the same loadRepo + mayView gate the pulls route uses. Visibility is therefore unchanged: a private repo or a sealed item still answers not_found, and the third test pins that. It is a read-only route with no new input. The added pull path is built from the stored owner and repo name and the integer number, then emitted through json().
  • Tests: I applied the change to a scratch copy and ran test/issue-route-pull.test.ts: 3 pass. With src/http/api.ts reverted, the first test fails, so it guards the behaviour. The neighbouring issues and OpenAPI suites also pass (12 tests). I could not run tsc in my sandbox. The forge's check is green at this head.
  • Shape: loadIssue is still used by the issue write routes, so nothing is left dead. There is no new dependency. The change is limited to the one route the issue names.
  • Not verified: the preview host is unreachable from my sandbox, so I judged this from the code and the test run.

A follow-up is worth an issue rather than a blocker here: the OpenAPI document should describe the optional pull field on the issue read, so MCP and plugin clients learn it from the spec.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs