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
- Create a separate Statistics instance per crawler
- Remove manual startCapturing() calls if the crawler already manages capture
- Serialize crawlers sharing an instance, or stopCapturing before restarting
- 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
- One Statistics instance per crawler
- Do not call startCapturing manually when the crawler manages it
- Track started instances with WeakSet in shared code
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
- OwnedOrInjected value is already initialized
- RequestList sources are already loading or were loaded.
- This crawler instance is already running, you can add more r
- fetchNextRequest called on an uninitialized crawler
- The `response` property is not available. This might mean th
AI-assisted analysis of apify/crawlee@dbe57fb09c (2026-08-30).
Data as JSON: /api/errors/6cf64ab443faca8b.
Report an issue: GitHub.