github/copilot-sdk · error

Cannot log after the factory run has settled

Error message

Cannot log after the factory run has settled

What it means

FactoryLogSink.enqueue (logger factory) queues log lines and flushes them on a 10ms delay while the factory run is live. Once the run has settled, closed is set to true and further logging is rejected — late logs could never be delivered and would silently vanish, so the library surfaces this instead.

Solutions

  1. Ensure all logging happens before the factory run's promises settle — await async work inside the thunk.
  2. Guard late log calls: capture the error and drop or buffer logs after settle.
  3. Move long-running background work outside the factory run, with its own lifecycle.

Example fix

// before
setTimeout(() => ctx.log("info", "done"), 100); // run already settled
// after
await new Promise((r) => setTimeout(r, 100));
ctx.log("info", "done");
Defensive patterns

Strategy: try-catch

Try / catch

try { ctx.log('info', 'done'); } catch (e) { if (String(e.message).includes('settled')) return; throw e; }

Prevention

When it happens

Trigger: Invoking ctx.log (enqueue) after the factory run settled: fire-and-forget promises that resolve late, setTimeout/setInterval callbacks, or event listeners that survive past the run's completion.

Common situations: A thunk that starts a background fetch and logs its result after Promise.all resolved; debounced code logging on a timer that outlives the run; unawaited async work inside a factory.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of github/copilot-sdk@cd8cf15dc3 (2026-09-09). Data as JSON: /api/errors/2913c8d3c126a4b3. Report an issue: GitHub.

Appendix: source

Thrown at nodejs/src/session.ts:253

            return previous;
        })
    );
}

class FactoryProgressBuffer {
    private nextSeq = 0;
    private pending: FactoryLogLine[] = [];
    private flushTimer?: ReturnType<typeof setTimeout>;
    private flushTail: Promise<void> = Promise.resolve();
    private flushError: unknown;
    private flushFailed = false;
    private closed = false;

    constructor(private readonly send: (lines: FactoryLogLine[]) => Promise<void>) {}

    enqueue(kind: FactoryLogLine["kind"], text: string): void {
        if (this.closed) {
            throw new Error("Cannot log after the factory run has settled");
        }

        this.pending.push({ seq: this.nextSeq++, kind, text });
        this.scheduleFlush();
    }

    async flush(): Promise<void> {
        this.clearFlushTimer();
        const lines = this.pending.splice(0);
        if (lines.length > 0) {
            this.flushTail = this.flushTail.then(async () => {
                try {
                    await this.send(lines);
                } catch (error) {
                    if (!this.flushFailed) {
                        this.flushFailed = true;
                        this.flushError = error;
                    }

View on GitHub (pinned to cd8cf15dc3)