diff --git a/src/queue/processors.ts b/src/queue/processors.ts index c122f847e7..f4623c4de7 100644 --- a/src/queue/processors.ts +++ b/src/queue/processors.ts @@ -6739,7 +6739,10 @@ async function handleIssueWebhookEvent( installationId, payload.repository.full_name, issue.number, - issueSettings.newAccountLabel!, + // #8687: mirror the PR-side twins (processors.ts:2765,3123) -- degrade to the "new-account" + // default rather than a bare non-null assertion, so a settings source that omits the field + // (e.g. a manifest overlay) labels consistently across the issue and PR paths. + issueSettings.newAccountLabel ?? "new-account", { createMissingLabel: issueSettings.createMissingLabel, mode: newAccountMode }, ).catch( /* v8 ignore next -- fail-safe: a label-application failure must never block the rest of the handler */ diff --git a/test/unit/queue-3.test.ts b/test/unit/queue-3.test.ts index a8721cca01..759c4760b5 100644 --- a/test/unit/queue-3.test.ts +++ b/test/unit/queue-3.test.ts @@ -4490,6 +4490,45 @@ describe("queue processors", () => { expect(seen.labels).not.toContain("new-account"); }); + it("account-age throttle (#8687 issue path): falls back to the default 'new-account' label when settings omit newAccountLabel", async () => { + const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem() }); + await upsertInstallation(env, { + installation: { id: 123, account: { login: "JSONbored", id: 1, type: "User" }, target_type: "User", repository_selection: "all", permissions: { metadata: "read", issues: "write" }, events: ["issues"] }, + repositories: [{ name: "gittensory", full_name: "JSONbored/gittensory", private: false, owner: { login: "JSONbored" } }], + }); + await upsertRepositorySettings(env, { + repoFullName: "JSONbored/gittensory", + autonomy: { close: "auto", review_state_label: "auto" }, + }); + await upsertRepoFocusManifest(env, "JSONbored/gittensory", { settings: { accountAgeThresholdDays: 30 } }); + // The DB default normally always populates newAccountLabel; strip it at the one settings-resolution seam so + // the issue-side call site's `?? "new-account"` fallback (previously a bare non-null assertion that would + // have labeled with `undefined`) is exercised end-to-end, matching the already-correct PR-side twins. + const original = repositorySettingsModule.resolveRepositorySettings; + const settingsSpy = vi.spyOn(repositorySettingsModule, "resolveRepositorySettings").mockImplementation(async (e, r) => { + const resolved = { ...(await original(e, r)) }; + delete resolved.newAccountLabel; + return resolved; + }); + const seen = { labels: [] as string[], closed: false }; + vi.stubGlobal("fetch", stubIssueAccountAgeFetch(62, new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString(), seen)); + + await processJob(env, { + type: "github-webhook", + deliveryId: "account-age-issue-default-label-8687", + eventName: "issues", + payload: { + action: "opened", + installation: { id: 123, account: { login: "JSONbored", id: 1, type: "User" } }, + repository: { name: "gittensory", full_name: "JSONbored/gittensory", private: false, owner: { login: "JSONbored" } }, + issue: { number: 62, title: "Newbie's issue", state: "open", user: { login: "newbie" }, labels: [], body: "x" }, + }, + }); + + expect(seen.labels).toContain("new-account"); + settingsSpy.mockRestore(); + }); + it("account-age throttle (#2561 issue path): the repo OWNER's own issue is never labeled even on a brand-new account", async () => { const env = createTestEnv({ GITHUB_APP_PRIVATE_KEY: await generatePrivateKeyPem() }); await upsertInstallation(env, {