thedotmack/claude-mem · error
failed to re-enqueue generation job on operator retry
Error message
failed to re-enqueue generation job on operator retry
What it means
The operator retry endpoint updates the job row to queued and then re-publishes it to the BullMQ event queue (a best-effort remove of the old bullmq_job_id, then queue.add with the retry payload). If publishing throws, this warning logs and the row is left queued-but-unpublished: by design the API never claims 'enqueued' when it could not publish, and startup reconciliation publishes such rows on next boot.
Source
Thrown at src/server/routes/v1/ServerV1PostgresRoutes.ts:1567
requestId: req.requestId ?? null,
retriedCount,
},
});
// Re-enqueue to BullMQ. If the queue is unavailable we leave the row in
// queued state and reconciliation will publish it on next startup —
// never lie about "enqueued" when we couldn't publish.
const queue = this.resolveEventQueueForRetry(updatedRow as { source_type: string });
if (queue && updatedRow) {
try {
const bullmqJobId = (updatedRow as { bullmq_job_id: string | null }).bullmq_job_id;
if (bullmqJobId) {
// Best effort remove first so a terminal-state slot doesn't block.
try { await queue.remove(bullmqJobId); } catch { /* terminal slot may be missing — ok */ }
await queue.add(bullmqJobId, retryBullmqPayload as never);
}
} catch (error) {
logger.warn('SYSTEM', 'failed to re-enqueue generation job on operator retry', {
jobId: id,
requestId: req.requestId ?? null,
error: error instanceof Error ? error.message : String(error),
});
}
}
const refreshed = await repo.getByIdForScope({ id, projectId: current.projectId, teamId });
if (!refreshed) {
res.status(404).json({ error: 'NotFound', message: 'Generation job not found' });
return null;
}
await this.auditWrite(req, 'generation_job.retried_by_operator', refreshed.id, refreshed.projectId, {
previousStatus: current.status,
currentStatus: refreshed.status,
retriedCount,
requestId: req.requestId ?? null,View on GitHub (pinned to e2d1df569a)
Solutions
- Verify Redis from the server host: redis-cli -u "$REDIS_URL" ping
- Read the warn's error field: ioredis connection errors point at Redis, serialization errors point at the retry payload shape
- Restart the API: startup reconciliation publishes queued-but-unpublished rows, completing the retry
- Align queue names, prefixes, and REDIS_URL with the deployment if they drifted
Example fix
// before
await queue.add(bullmqJobId, retryBullmqPayload as never);
// after: retry the publish, then rely on startup reconciliation if it still fails
let published = false;
for (let attempt = 1; attempt <= 3 && !published; attempt++) {
try {
await queue.add(bullmqJobId, retryBullmqPayload as never);
published = true;
} catch (error) {
if (attempt === 3) {
logger.warn('SYSTEM', 'failed to re-enqueue generation job on operator retry', {
jobId: id,
error: error instanceof Error ? error.message : String(error),
});
} else {
await new Promise((resolve) => setTimeout(resolve, 100 * attempt));
}
}
} Defensive patterns
Strategy: retry
Validate before calling
// Before triggering operator retries, confirm the queue backend is reachable: await queue.client.ping(); // the ioredis client BullMQ uses // If this throws, fix Redis first: retries would only queue rows, not jobs.
Type guard
function isRedisConnectionError(error: unknown): boolean {
const name = (error as { name?: string } | null)?.name;
const message = error instanceof Error ? error.message : String(error);
return name === 'MaxRetriesPerRequestError' || /connection|ECONNREFUSED|ETIMEDOUT/i.test(message);
} Try / catch
try {
if (bullmqJobId) {
try { await queue.remove(bullmqJobId); } catch { /* terminal slot may be missing */ }
await queue.add(bullmqJobId, retryBullmqPayload as never);
}
} catch (error) {
if (isRedisConnectionError(error)) {
// leave the row queued; startup reconciliation re-publishes it — do not report 'enqueued'
}
logger.warn('SYSTEM', 'failed to re-enqueue generation job on operator retry', {
jobId: id, requestId: req.requestId ?? null, error: error instanceof Error ? error.message : String(error),
});
} Prevention
- Add a Redis health check to the API readiness probe so retries are rejected while the queue is down
- Pin queue names and prefixes in shared config so producer and consumer cannot drift
- Alert on queued-but-unpublished rows to see reconciliation lag before users do
- Version BullMQ job payloads backward-compatibly so serialization never fails on old workers
When it happens
Trigger: POSTing an operator retry for a generation job while Redis is down or unreachable (BullMQ add fails), the REDIS_URL or queue name changed between deploys, or the retry payload cannot be serialized for the queue.
Common situations: Redis restart or failover mid-retry; queue prefix or name drift after a config change; Redis maxmemory pressure; network policy blocking the Redis port from new pods.
Related errors
- Invalid CLAUDE_MEM_QUEUE_ENGINE=${raw}; expected sqlite or b
- Invalid CLAUDE_MEM_REDIS_MODE=${value}; expected external, m
- Invalid CLAUDE_MEM_REDIS_PORT=${value}; expected a TCP port
- CLAUDE_MEM_REDIS_URL must use redis:// or rediss://
- Invalid Redis database in CLAUDE_MEM_REDIS_URL: ${parsed.pat
AI-assisted analysis of thedotmack/claude-mem@e2d1df569a (2026-08-20).
Data as JSON: /api/errors/57da44f1a3683c5a.
Report an issue: GitHub.