diff --git a/src/core/minions/queue.ts b/src/core/minions/queue.ts index 0d0780fdb..ccf71cd96 100644 --- a/src/core/minions/queue.ts +++ b/src/core/minions/queue.ts @@ -133,27 +133,12 @@ export class MinionQueue { // 1. Idempotency fast path — if a row already exists for this key, return it // without doing any other work. The unique partial index guarantees // no second row can be inserted with the same non-null key. - // - // Dead/cancelled jobs represent permanently-failed work whose - // idempotency slot must be freed so a fresh attempt can be inserted. - // We NULL the key (preserving the row for audit) and fall through - // to the INSERT path below. if (opts?.idempotency_key) { const existing = await tx.executeRaw>( `SELECT * FROM minion_jobs WHERE idempotency_key = $1`, [opts.idempotency_key] ); - if (existing.length > 0) { - const existingJob = rowToMinionJob(existing[0]); - if (existingJob.status === 'dead' || existingJob.status === 'cancelled') { - await tx.executeRaw( - `UPDATE minion_jobs SET idempotency_key = NULL WHERE id = $1`, - [existingJob.id] - ); - } else { - return existingJob; - } - } + if (existing.length > 0) return rowToMinionJob(existing[0]); } // 1b. Submission-time backpressure for high-frequency named jobs. diff --git a/test/minions.test.ts b/test/minions.test.ts index 0909d7e44..3f6bf3c07 100644 --- a/test/minions.test.ts +++ b/test/minions.test.ts @@ -1582,73 +1582,6 @@ describe('MinionQueue: Idempotency', () => { expect(j2.id).toBe(j1.id); expect(j2.data).toEqual({ v: 1 }); // first wins }); - - test('dead job with idempotency_key allows re-submission', async () => { - const j1 = await queue.add('test-synth', { prompt: 'synthesize' }, { - idempotency_key: 'dream:synth:test:abc123', - max_attempts: 1, - }); - await engine.executeRaw( - `UPDATE minion_jobs SET status = 'dead', finished_at = now() WHERE id = $1`, - [j1.id] - ); - const j2 = await queue.add('test-synth', { prompt: 'synthesize' }, { - idempotency_key: 'dream:synth:test:abc123', - max_attempts: 8, - }); - expect(j2.id).not.toBe(j1.id); - expect(j2.status).toBe('waiting'); - const oldRow = await engine.executeRaw<{ idempotency_key: string | null }>( - `SELECT idempotency_key FROM minion_jobs WHERE id = $1`, - [j1.id] - ); - expect(oldRow[0].idempotency_key).toBeNull(); - }); - - test('cancelled job with idempotency_key allows re-submission', async () => { - const j1 = await queue.add('test-synth', {}, { - idempotency_key: 'dream:synth:test:cancel', - }); - await engine.executeRaw( - `UPDATE minion_jobs SET status = 'cancelled', finished_at = now() WHERE id = $1`, - [j1.id] - ); - const j2 = await queue.add('test-synth', {}, { - idempotency_key: 'dream:synth:test:cancel', - }); - expect(j2.id).not.toBe(j1.id); - expect(j2.status).toBe('waiting'); - }); - - test('completed job with idempotency_key still blocks re-submission', async () => { - const j1 = await queue.add('sync', {}, { - idempotency_key: 'dream:synth:test:completed', - }); - await engine.executeRaw( - `UPDATE minion_jobs SET status = 'completed', finished_at = now() WHERE id = $1`, - [j1.id] - ); - const j2 = await queue.add('sync', {}, { - idempotency_key: 'dream:synth:test:completed', - }); - expect(j2.id).toBe(j1.id); - expect(j2.status).toBe('completed'); - }); - - test('active job with idempotency_key still blocks re-submission', async () => { - const j1 = await queue.add('sync', {}, { - idempotency_key: 'dream:synth:test:active', - }); - await engine.executeRaw( - `UPDATE minion_jobs SET status = 'active' WHERE id = $1`, - [j1.id] - ); - const j2 = await queue.add('sync', {}, { - idempotency_key: 'dream:synth:test:active', - }); - expect(j2.id).toBe(j1.id); - expect(j2.status).toBe('active'); - }); }); // --- v7 child_done auto-post ---