apify/crawlee · error · Error

Statistics.startCapturing() was already called - this instan

Error message

Statistics.startCapturing() was already called - this instance is already capturing.

What it means

A Statistics instance can drive only one logging interval and one PERSIST_STATE listener. Calling startCapturing() twice on the same instance (e.g. one instance shared by concurrently running crawlers) would orphan the first capture, so the library fails loudly with this error.

Source

Thrown at packages/core/src/crawlers/statistics.ts:578

            requestAvgFinishedDurationMillis:
                Math.round(requestTotalFinishedDurationMillis / requestsFinished) || Infinity,
            requestsFinishedPerMinute: Math.round(requestsFinished / totalMinutes) || 0,
            requestsFailedPerMinute: Math.floor(requestsFailed / totalMinutes) || 0,
            requestTotalDurationMillis: requestTotalFinishedDurationMillis + requestTotalFailedDurationMillis,
            requestsTotal: requestsFailed + requestsFinished,
            crawlerRuntimeMillis: totalMillis,
        };
    }

    /**
     * Initializes the key value store for persisting the statistics,
     * displaying the current state in predefined intervals
     */
    async startCapturing() {
        // A single instance drives one logging interval and one PERSIST_STATE listener, so a second concurrent
        // capture (e.g. sharing one instance across crawlers running at once) would orphan the first. Fail loudly.
        if (this.#logInterval) {
            throw new Error('Statistics.startCapturing() was already called - this instance is already capturing.');
        }

        await this.#recoverableState.initialize();

        // After the load, so that a restored record keeps the timestamp of the run it belongs to.
        if (this.state.crawlerStartedAt === null) {
            this.state.crawlerStartedAt = new Date();
        }

        this.#logInterval = setInterval(() => {
            this.log.info(this.#logMessage, {
                ...this.calculate(),
                retryHistogram: this.requestRetryHistogram,
            });
        }, this.#logIntervalMillis);
    }

    /**

View on GitHub (pinned to dbe57fb09c)

Solutions

  1. Create a separate Statistics instance per crawler
  2. Remove manual startCapturing() calls if the crawler already manages capture
  3. Serialize crawlers sharing an instance, or stopCapturing before restarting
  4. Use Statistics from configuration so each crawler gets its own

Example fix

// before
const shared = new Statistics(); await Promise.all([c1.run(), c2.run()]); // both use shared
// after
await Promise.all([c1.run(), c2.run()]); // each crawler creates its own Statistics internally
Defensive patterns

Strategy: validation

Validate before calling

if (stats.#logInterval /* or track your own flag */) throw new Error('already capturing');
// caller-side: keep a Set of instances already started
const started = new WeakSet(); if (!started.has(stats)) { started.add(stats); await stats.startCapturing(); }

Try / catch

try { await stats.startCapturing(); } catch (e) { if (String(e).includes('already capturing')) return; throw e; } // treat as idempotent no-op

Prevention

When it happens

Trigger: Calling crawler.stats.startCapturing() manually when the crawler already starts it; sharing a single Statistics instance across two crawlers running at once; re-running startCapturing after an earlier successful call.

Common situations: Multiple crawler.run() invocations reusing one Statistics object; parallel crawlers constructed with the same stats instance; test suites invoking startCapturing in setup for each test on a shared instance.

Related errors


AI-assisted analysis of apify/crawlee@dbe57fb09c (2026-08-30). Data as JSON: /api/errors/6cf64ab443faca8b. Report an issue: GitHub.