can1357/oh-my-pi · error
Hindsight retain queue is closed.
Error message
Hindsight retain queue is closed.
What it means
The Hindsight retain queue throws when enqueue is called after the queue has been closed. Closing signals shutdown (no further accepts); enqueueing afterwards is a programming error since items would never be flushed.
Source
Thrown at packages/coding-agent/src/hindsight/state.ts:86
*/
export class HindsightRetainQueue {
readonly #state: HindsightSessionState;
#items: PendingRetainItem[] = [];
#timer?: NodeJS.Timeout;
#flushing?: Promise<void>;
#closed = false;
constructor(state: HindsightSessionState) {
this.#state = state;
}
get depth(): number {
return this.#items.length;
}
enqueue(content: string, context?: string): void {
if (this.#closed) {
throw new Error("Hindsight retain queue is closed.");
}
this.#items.push({ content, context, timestamp: new Date() });
if (this.#items.length >= RETAIN_FLUSH_BATCH_SIZE) {
void this.flush();
return;
}
if (!this.#timer) {
this.#timer = setTimeout(() => {
void this.flush();
}, RETAIN_FLUSH_INTERVAL_MS);
// Don't pin the event loop alive just for a pending retain flush.
this.#timer.unref?.();
}
}
async flush(): Promise<void> {
if (this.#timer) {View on GitHub (pinned to 9690622007)
Solutions
- Check/track the closed state before enqueueing (guard call sites during shutdown)
- Ensure all producers stop before calling close() — cancel or await pending hooks
- Catch this error in fire-and-forget retain paths and drop the item, since flush infrastructure is gone
Example fix
// before
queue.enqueue(memoryText); // may throw after shutdown
// after
try {
queue.enqueue(memoryText);
} catch (err) {
if (err instanceof Error && err.message.includes("queue is closed")) {
logger.debug("Dropped retain after Hindsight shutdown");
return;
}
throw err;
} Defensive patterns
Strategy: try-catch
Validate before calling
// track lifecycle yourself if (hindsightState.isClosed) return; // drop instead of enqueue
Type guard
function isQueueClosedError(err: unknown): err is Error {
return err instanceof Error && err.message === "Hindsight retain queue is closed.";
} Try / catch
try {
queue.enqueue(text, context);
} catch (err) {
if (isQueueClosedError(err)) {
logger.debug("Retain dropped: Hindsight already shut down");
return;
}
throw err;
} Prevention
- Stop all retain producers before calling close()
- Await pending async hooks/flushes during shutdown ordering
- Make late retain calls no-ops in shutdown paths
- Prefer flush() then close() in teardown code
When it happens
Trigger: Calling queue.enqueue(content) after close()/shutdown of the Hindsight state; a background hook or session-end handler firing after teardown; retaining during process exit handling.
Common situations: Race between an async flush/shutdown and a late retain call; session teardown ordering where a subscriber fires after the queue closes; long-running background tasks outliving the Hindsight lifecycle.
Related errors
- DAP adapter ${this.adapter.name} is not running
- Debug session ${root.id} is still active. Terminate it befor
- Cannot ${action} on a disposed JS runtime
- Cannot set cwd on a disposed JS runtime
- ${this.#options.languageName} kernel is not running
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/4556ce79368b2365.
Report an issue: GitHub.