# bunqueue v2.8.17

Fixes 1 bug. Retrying a job that reached failed through the lock-expiry path failed with UNIQUE constraint failed: jobs.id (src/application/lockManager.ts): handleMaxStallsExceeded moved the job to the DLQ using only in-memory state…

Canonical: https://bunqueue.dev/blog/bunqueue-v2-8-17/

---

<!-- Generated from changelog.md by docs/scripts/generate-release-posts.ts. Edit the changelog and regenerate. -->

Fixes 1 bug. Retrying a job that reached failed through the lock-expiry path failed with UNIQUE constraint failed: jobs.id (src/application/lockManager.ts): handleMaxStallsExceeded moved the job to the DLQ using only in-memory state…

![](https://github.com/egeominotti.png?size=64) [egeominotti](https://github.com/egeominotti)

#### To install bunqueue v2.8.17

```bash title="bun"
bun add bunqueue@2.8.17
```

```bash title="npm"
npm install bunqueue@2.8.17
```

#### To upgrade bunqueue

```bash
bun add bunqueue@latest
```

## Bugfixes

- **Retrying a job that reached `failed` through the lock-expiry path failed with `UNIQUE constraint failed: jobs.id`** (`src/application/lockManager.ts`): `handleMaxStallsExceeded` moved the job to the DLQ using only in-memory state (`shard.addToDlq` + `jobIndex.set`). Unlike its three sibling paths, `ack.moveFailedJobToDlq` (max attempts), `stallDetection.moveStalliedJobToDlq` (heartbeat stall), and the startup recovery in `backgroundTasks`, it never called `storage.saveDlqEntry(entry)` nor `storage.deleteJob(jobId)`. So the `jobs` row survived in SQLite as an orphan (state `active`) and the DLQ entry lived only in memory. On retry, `dlqManager.retryDlqJob` re-INSERTs the job with its original id via the plain `INSERT INTO jobs` statement (not `INSERT OR REPLACE` like `insertResult`/`insertCron`), and the surviving orphan row raised the UNIQUE violation, failing the retry; a restart in that window also re-recovered the stale `active` row. The lock-expiry DLQ move now persists like its siblings (capture the `DlqEntry`, then `saveDlqEntry` + `deleteJob`), restoring the single-table-residency invariant. `deleteJob` also evicts the id from the write buffer, so a non-durable job cannot later flush a stale INSERT and re-orphan.

## Thanks to 1 contributor!

- [@egeominotti](https://github.com/egeominotti)

---

[← bunqueue v2.8.16](https://bunqueue.dev/blog/bunqueue-v2-8-16/) · [Release on GitHub](https://github.com/egeominotti/bunqueue/releases/tag/v2.8.17) · [Commits since v2.8.16](https://github.com/egeominotti/bunqueue/compare/v2.8.16...v2.8.17) · [Full changelog](https://bunqueue.dev/changelog/) · [All posts](https://bunqueue.dev/blog/)
