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

  1. Check/track the closed state before enqueueing (guard call sites during shutdown)
  2. Ensure all producers stop before calling close() — cancel or await pending hooks
  3. 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

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


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/4556ce79368b2365. Report an issue: GitHub.