From bcd69897fdb3b515693b105e387d9b98aa4ae99b Mon Sep 17 00:00:00 2001 From: MarcoDotIO Date: Mon, 26 Oct 2026 12:38:53 -0400 Subject: [PATCH] fix(server): unlink_pull_request reports a dismissed stack PR as not linked A pull request the user removed from a stack stays on the thread as a hidden stack-dismissed record. list_thread_pull_requests, link_pull_request and watch_pull_request already treat that record as not linked, but unlink_pull_request counted it, dispatched an unlink and answered wasLinked=true. Skip dismissed records there too. Co-Authored-By: Claude Opus 5.5 --- .../src/mcp/toolkits/pullRequests/handlers.test.ts | 14 ++++++++++++++ .../src/mcp/toolkits/pullRequests/handlers.ts | 6 +++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/apps/server/src/mcp/toolkits/pullRequests/handlers.test.ts b/apps/server/src/mcp/toolkits/pullRequests/handlers.test.ts index 507381531ac49..14f23bbea6dc5 100644 --- a/apps/server/src/mcp/toolkits/pullRequests/handlers.test.ts +++ b/apps/server/src/mcp/toolkits/pullRequests/handlers.test.ts @@ -498,6 +498,20 @@ describe("pull request toolkit handlers", () => { }), ); + it.effect("reports a dismissed stack member as not linked and leaves it dismissed", () => + Effect.gen(function* () { + const harness = yield* makeHarness({ + thread: makeThread([makeLink(9, { source: "stack-dismissed" })]), + }); + const result = yield* harness.call("unlink_pull_request", { + repository: "t3tools/t3code", + number: 9, + }); + expect(result.wasLinked).toBe(false); + expect(yield* Ref.get(harness.commands)).toEqual([]); + }), + ); + it("reports an older Forgejo link's HTTP port when listing thread links", () => { const result = listThreadPullRequests( makeThread([ diff --git a/apps/server/src/mcp/toolkits/pullRequests/handlers.ts b/apps/server/src/mcp/toolkits/pullRequests/handlers.ts index ca37361d244c1..1fd46e8ba8d30 100644 --- a/apps/server/src/mcp/toolkits/pullRequests/handlers.ts +++ b/apps/server/src/mcp/toolkits/pullRequests/handlers.ts @@ -392,7 +392,11 @@ const make = Effect.gen(function* () { const thread = yield* requireThread(PullRequestUnlinkFailedError, input.threadId); const project = yield* projectOf(thread, PullRequestUnlinkFailedError); const target = yield* resolveTarget(input, project); - if (!threadPullRequestsOf(thread).some((link) => threadPullRequestKeysEqual(link, target))) + if ( + !threadPullRequestsOf(thread).some( + (link) => link.source !== "stack-dismissed" && threadPullRequestKeysEqual(link, target), + ) + ) return { host: target.host, repository: target.repository,