From 1350753f5f6e180f7ccf5ec489bcd256e400a05e Mon Sep 17 00:00:00 2001 From: plind-dm <59729252+plind-dm@users.noreply.github.com> Date: Fri, 3 Apr 2026 01:53:57 +0900 Subject: [PATCH 1/3] fix(api): include attachment metadata in heartbeat-context response MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Agents receiving issue context via GET /issues/:id/heartbeat-context had no way to discover file attachments — the endpoint returned issue metadata, ancestors, project, goal, and comment cursor but omitted attachments entirely. Users attaching files through the UI would then see agents ask for documents that were already uploaded. Fetch attachments in parallel with the existing queries and append a lightweight summary (id, filename, contentType, byteSize, contentPath) to the response so agents can detect and retrieve attached files on their first heartbeat without an extra round-trip. Closes #2536 --- .../src/__tests__/issues-goal-context-routes.test.ts | 2 ++ server/src/routes/issues.ts | 11 ++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/server/src/__tests__/issues-goal-context-routes.test.ts b/server/src/__tests__/issues-goal-context-routes.test.ts index 25ce2042..d8036797 100644 --- a/server/src/__tests__/issues-goal-context-routes.test.ts +++ b/server/src/__tests__/issues-goal-context-routes.test.ts @@ -10,6 +10,7 @@ const mockIssueService = vi.hoisted(() => ({ findMentionedProjectIds: vi.fn(), getCommentCursor: vi.fn(), getComment: vi.fn(), + listAttachments: vi.fn(), })); const mockProjectService = vi.hoisted(() => ({ @@ -129,6 +130,7 @@ describe("issue goal context routes", () => { latestCommentAt: null, }); mockIssueService.getComment.mockResolvedValue(null); + mockIssueService.listAttachments.mockResolvedValue([]); mockProjectService.getById.mockResolvedValue({ id: legacyProjectLinkedIssue.projectId, companyId: "company-1", diff --git a/server/src/routes/issues.ts b/server/src/routes/issues.ts index 5eb0b83e..b7f7bdee 100644 --- a/server/src/routes/issues.ts +++ b/server/src/routes/issues.ts @@ -448,11 +448,12 @@ export function issueRoutes(db: Db, storage: StorageService) { ? req.query.wakeCommentId.trim() : null; - const [{ project, goal }, ancestors, commentCursor, wakeComment] = await Promise.all([ + const [{ project, goal }, ancestors, commentCursor, wakeComment, attachments] = await Promise.all([ resolveIssueProjectAndGoal(issue), svc.getAncestors(issue.id), svc.getCommentCursor(issue.id), wakeCommentId ? svc.getComment(wakeCommentId) : null, + svc.listAttachments(issue.id), ]); res.json({ @@ -499,6 +500,14 @@ export function issueRoutes(db: Db, storage: StorageService) { wakeComment && wakeComment.issueId === issue.id ? wakeComment : null, + attachments: attachments.map((a) => ({ + id: a.id, + filename: a.originalFilename, + contentType: a.contentType, + byteSize: a.byteSize, + contentPath: `/api/attachments/${a.id}/content`, + createdAt: a.createdAt, + })), }); }); From 620a5395d70cb55cffec9e7b7b646fbfe2b2607e Mon Sep 17 00:00:00 2001 From: plind <59729252+plind-dm@users.noreply.github.com> Date: Fri, 3 Apr 2026 02:01:46 +0900 Subject: [PATCH 2/3] Update server/src/routes/issues.ts LGTM Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- server/src/routes/issues.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/routes/issues.ts b/server/src/routes/issues.ts index b7f7bdee..cc9dc304 100644 --- a/server/src/routes/issues.ts +++ b/server/src/routes/issues.ts @@ -505,7 +505,7 @@ export function issueRoutes(db: Db, storage: StorageService) { filename: a.originalFilename, contentType: a.contentType, byteSize: a.byteSize, - contentPath: `/api/attachments/${a.id}/content`, + contentPath: withContentPath(a).contentPath, createdAt: a.createdAt, })), }); From 3513b60dbcef07e5d56ed5add8e9eeeb5fbd6ba4 Mon Sep 17 00:00:00 2001 From: plind-dm <59729252+plind-dm@users.noreply.github.com> Date: Sun, 5 Apr 2026 21:57:15 +0900 Subject: [PATCH 3/3] test: assert attachments field in heartbeat-context response Add missing assertion for the empty attachments array in the heartbeat-context test to verify the field mapping is present. --- server/src/__tests__/issues-goal-context-routes.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/server/src/__tests__/issues-goal-context-routes.test.ts b/server/src/__tests__/issues-goal-context-routes.test.ts index d8036797..db2bdbc4 100644 --- a/server/src/__tests__/issues-goal-context-routes.test.ts +++ b/server/src/__tests__/issues-goal-context-routes.test.ts @@ -199,5 +199,6 @@ describe("issue goal context routes", () => { }), ); expect(mockGoalService.getDefaultCompanyGoal).not.toHaveBeenCalled(); + expect(res.body.attachments).toEqual([]); }); });