can1357/oh-my-pi · error · Error
Writer closed
Error message
Writer closed
What it means
appendSync() on an indexed session-storage writer throws 'Writer closed' when the writer has already been closed via close()/dispose(). It also rethrows any stored #error from earlier failed remote publishes, so an append on a poisoned writer surfaces the original I/O failure.
Source
Thrown at packages/coding-agent/src/session/indexed-session-storage.ts:520
this.#onError?.(error);
return error;
}
#trackPromise(promise: Promise<void>): Promise<void> {
const next = this.#pendingChain.then(async () => {
if (this.#error) throw this.#error;
try {
await promise;
} catch (err) {
throw this.#recordError(err);
}
});
this.#pendingChain = next.catch(() => {});
return next;
}
appendSync(line: string): void {
if (this.#closed) throw new Error("Writer closed");
if (this.#error) throw this.#error;
// Local index is updated immediately; remote publish stays ordered on the
// path queue. Callers that need remote durability still await append()/flush().
const mtimeMs = this.#storage._appendForWriter(this.#path, line);
void this.#trackPromise(this.#storage._queueAppend(this.#path, line, mtimeMs, () => this.#error));
}
async append(line: string): Promise<void> {
if (this.#closed) throw new Error("Writer closed");
if (this.#error) throw this.#error;
const mtimeMs = this.#storage._appendForWriter(this.#path, line);
await this.#trackPromise(this.#storage._queueAppend(this.#path, line, mtimeMs, () => this.#error));
}
async flush(): Promise<void> {
if (this.#error) throw this.#error;
await this.#pendingChain;
if (this.#error) throw this.#error;View on GitHub (pinned to 9690622007)
Solutions
- Stop issuing appends once close() has been called — check a closed flag in the caller
- Catch this error in shutdown/telemetry paths and drop the late write instead of crashing
- Inspect the sticky #error (if rethrown) to fix the original remote-publish failure before reopening a new writer
- Create a fresh writer for any post-close writes rather than reusing the closed one
Example fix
// before
writer.close();
writer.appendSync(line); // throws
// after
writer.close();
if (!closed) writer.appendSync(line); // guard with your own closed flag
// or
try { writer.appendSync(line); } catch (e) { logger.debug('late append dropped', { e }); } Defensive patterns
Strategy: try-catch
Validate before calling
// track writer lifecycle in the caller: // let writerOpen = true; // writer.close(); writerOpen = false; // if (writerOpen) writer.appendSync(line);
Try / catch
try {
writer.appendSync(line);
} catch (err) {
if (err instanceof Error && err.message === 'Writer closed') {
logger.debug('dropped append after close', { path });
return;
}
throw err; // sticky #error from a failed remote publish — surface it
} Prevention
- Await all pending appends before closing the writer
- Unregister logging/session callbacks before close so nothing writes afterward
- If a closed writer rethrows a different stored error, fix the original publish failure before reopening
- Use a single owner of the writer lifecycle to avoid double-close races
When it happens
Trigger: Calling writer.appendSync(line) after close() was called, or after a prior async append failed and set the writer's sticky #error state.
Common situations: A logging/session-persistence callback still firing after session close; double-close paths where two components both try to finalize; a crashed remote publisher poisoned the writer and later sync appends surface the stored error.
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/7d1bb54f7324d3d7.
Report an issue: GitHub.