From 6408e4aa8aa60d7705f89ee1f7f8badcc2cfcd4f Mon Sep 17 00:00:00 2001 From: Suhrid Marwah Date: Thu, 9 Apr 2026 10:27:15 +0530 Subject: [PATCH 1/3] fix(scaffolder): handle after=0 in task events polling endpoint Signed-off-by: Suhrid Marwah --- plugins/scaffolder-backend/src/service/router.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index 9e84b38e39..f7c5913e61 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -951,7 +951,8 @@ export async function createRouter( isTaskAuthorized, }); - const after = Number(req.query.after) || undefined; + const after = + req.query.after !== undefined ? Number(req.query.after) : undefined; // cancel the request after 30 seconds. this aligns with the recommendations of RFC 6202. const timeout = setTimeout(() => { From 8a42f77daae98c1dc374d42ae30a1831b6ae2df2 Mon Sep 17 00:00:00 2001 From: Suhrid Marwah Date: Thu, 9 Apr 2026 10:46:26 +0530 Subject: [PATCH 2/3] chore(changeset): add changeset for after=0 fix Signed-off-by: Suhrid Marwah --- .changeset/fix-scaffolder-task-events-after-zero.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fix-scaffolder-task-events-after-zero.md diff --git a/.changeset/fix-scaffolder-task-events-after-zero.md b/.changeset/fix-scaffolder-task-events-after-zero.md new file mode 100644 index 0000000000..5ff9bc2de6 --- /dev/null +++ b/.changeset/fix-scaffolder-task-events-after-zero.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +--- + +Fix handling of `after=0` in task events endpoint From 1d95cd302d1fb70c210c9a2d20c918dff7c3e7b1 Mon Sep 17 00:00:00 2001 From: Suhrid Marwah Date: Thu, 9 Apr 2026 10:54:11 +0530 Subject: [PATCH 3/3] test(scaffolder): add test for after=0 query param handling Signed-off-by: Suhrid Marwah --- .../src/service/router.test.ts | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/plugins/scaffolder-backend/src/service/router.test.ts b/plugins/scaffolder-backend/src/service/router.test.ts index 223909f2c9..5b463ee75b 100644 --- a/plugins/scaffolder-backend/src/service/router.test.ts +++ b/plugins/scaffolder-backend/src/service/router.test.ts @@ -1572,6 +1572,46 @@ data: {"id":1,"taskId":"a-random-id","type":"completion","createdAt":"","body":{ }); expect(subscriber!.closed).toBe(true); }); + + it('should handle after=0 query param correctly', async () => { + const { router, taskBroker } = await createTestRouter(); + (taskBroker.get as jest.Mocked['get']).mockResolvedValue({ + id: 'a-random-id', + spec: {} as any, + status: 'completed', + createdAt: '', + secrets: { + __initiatorCredentials: JSON.stringify(credentials), + }, + createdBy: '', + }); + let subscriber: ZenObservable.SubscriptionObserver<{ + events: SerializedTaskEvent[]; + }>; + ( + taskBroker.event$ as jest.Mocked['event$'] + ).mockImplementation(() => { + return new ObservableImpl(observer => { + subscriber = observer; + observer.next({ events: [] }); + }); + }); + + const response = await request(router) + .get('/v2/tasks/a-random-id/events') + .query({ after: 0 }); + + expect(response.status).toEqual(200); + expect(response.body).toEqual([]); + + expect(taskBroker.event$).toHaveBeenCalledTimes(1); + expect(taskBroker.event$).toHaveBeenCalledWith({ + taskId: 'a-random-id', + after: 0, + }); + expect(subscriber!.closed).toBe(true); + }); + it('disallows users from seeing events for tasks they do not own', async () => { const { permissions, router, taskBroker } = await createTestRouter();