diff --git a/sdk/typescript/src/linear.ts b/sdk/typescript/src/linear.ts index fd14cc05e..f9386e206 100644 --- a/sdk/typescript/src/linear.ts +++ b/sdk/typescript/src/linear.ts @@ -83,9 +83,12 @@ export async function importLinearIssues(options: { ); } - const page = await projects.nodes[0]!.issues({ first: 50, filter }); - while (page.pageInfo.hasNextPage) await page.fetchNext(); - issues.push(...page.nodes); + let page = await projects.nodes[0]!.issues({ first: 50, filter }); + while (true) { + issues.push(...page.nodes); + if (!page.pageInfo.hasNextPage) break; + page = await page.fetchNext(); + } if (issues.length === 0) { throw new CodexSecurityError( `No open Linear issues matched project "${options.project}" and its filter.`, @@ -114,8 +117,13 @@ export async function importLinearIssues(options: { const imports: ImportedIssue[] = []; for (const issue of issues) { - const comments = await issue.comments({ first: 50 }); - while (comments.pageInfo.hasNextPage) await comments.fetchNext(); + let comments = await issue.comments({ first: 50 }); + const commentNodes = []; + while (true) { + commentNodes.push(...comments.nodes); + if (!comments.pageInfo.hasNextPage) break; + comments = await comments.fetchNext(); + } imports.push({ source: "linear", id: issue.identifier, @@ -123,7 +131,7 @@ export async function importLinearIssues(options: { text: [ `Title: ${issue.title}`, `\n${issue.description ?? ""}\n`, - ...comments.nodes.map( + ...commentNodes.map( ({ url, body }) => `\nURL: ${url}\n\n${body}\n`, ), ].join("\n\n"), diff --git a/sdk/typescript/tests-ts/cli-skills.test.ts b/sdk/typescript/tests-ts/cli-skills.test.ts index 252f8d15f..bdb2f1235 100644 --- a/sdk/typescript/tests-ts/cli-skills.test.ts +++ b/sdk/typescript/tests-ts/cli-skills.test.ts @@ -20,20 +20,26 @@ function linearIssue(identifier: string, comments: string[] = []) { body, url: `https://linear.app/example/issue/${identifier}#comment-${index}`, })); + const nextPage = { + nodes: nodes.slice(1), + pageInfo: { hasNextPage: false }, + async fetchNext() { + throw new Error("unexpected extra Linear comment page"); + }, + }; + const firstPage = { + nodes: nodes.slice(0, 1), + pageInfo: { hasNextPage: nodes.length > 1 }, + async fetchNext() { + return nextPage; + }, + }; return { identifier, title: `Fix ${identifier}`, description: `Synthetic evidence for ${identifier}`, url: `https://linear.app/example/issue/${identifier}`, - comments: async () => ({ - nodes: nodes.slice(0, 1), - pageInfo: { hasNextPage: nodes.length > 1 }, - async fetchNext() { - this.nodes.push(...nodes.slice(1)); - this.pageInfo.hasNextPage = false; - return this; - }, - }), + comments: async () => firstPage, }; } @@ -191,7 +197,7 @@ describe("CLI skill commands", () => { description, }; }, - } as ReturnType; + } as unknown as ReturnType; }, onCodex: (_args, output, processEnvironment) => { inputs = JSON.parse(output!.appServer!.prompt.split("\n").at(-1)!); @@ -308,16 +314,19 @@ describe("CLI skill commands", () => { environment: { LINEAR_ACCESS_TOKEN: "SYNTHETIC_OAUTH_TOKEN" }, linearClient: ({ accessToken }) => { expect(accessToken).toBe("SYNTHETIC_OAUTH_TOKEN"); + const nextPage = { + nodes: [linearIssue("SEC-124", ["Second issue comment"])], + pageInfo: { hasNextPage: false }, + async fetchNext() { + throw new Error("unexpected extra Linear issue page"); + }, + }; const page = { nodes: [linearIssue("SEC-123", ["First issue comment"])], pageInfo: { hasNextPage: true }, async fetchNext() { nextPages++; - this.nodes.push( - linearIssue("SEC-124", ["Second issue comment"]), - ); - this.pageInfo.hasNextPage = false; - return this; + return nextPage; }, }; return {