{"record":{"id":"6cf64ab443faca8b","repo":"apify/crawlee","slug":"statistics-startcapturing-was-already-called-t","errorCode":null,"errorMessage":"Statistics.startCapturing() was already called - this instance is already capturing.","messagePattern":"Statistics\\.startCapturing\\(\\) was already called - this instance is already capturing\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/core/src/crawlers/statistics.ts","lineNumber":578,"sourceCode":"            requestAvgFinishedDurationMillis:\n                Math.round(requestTotalFinishedDurationMillis / requestsFinished) || Infinity,\n            requestsFinishedPerMinute: Math.round(requestsFinished / totalMinutes) || 0,\n            requestsFailedPerMinute: Math.floor(requestsFailed / totalMinutes) || 0,\n            requestTotalDurationMillis: requestTotalFinishedDurationMillis + requestTotalFailedDurationMillis,\n            requestsTotal: requestsFailed + requestsFinished,\n            crawlerRuntimeMillis: totalMillis,\n        };\n    }\n\n    /**\n     * Initializes the key value store for persisting the statistics,\n     * displaying the current state in predefined intervals\n     */\n    async startCapturing() {\n        // A single instance drives one logging interval and one PERSIST_STATE listener, so a second concurrent\n        // capture (e.g. sharing one instance across crawlers running at once) would orphan the first. Fail loudly.\n        if (this.#logInterval) {\n            throw new Error('Statistics.startCapturing() was already called - this instance is already capturing.');\n        }\n\n        await this.#recoverableState.initialize();\n\n        // After the load, so that a restored record keeps the timestamp of the run it belongs to.\n        if (this.state.crawlerStartedAt === null) {\n            this.state.crawlerStartedAt = new Date();\n        }\n\n        this.#logInterval = setInterval(() => {\n            this.log.info(this.#logMessage, {\n                ...this.calculate(),\n                retryHistogram: this.requestRetryHistogram,\n            });\n        }, this.#logIntervalMillis);\n    }\n\n    /**","sourceCodeStart":560,"sourceCodeEnd":596,"githubUrl":"https://github.com/apify/crawlee/blob/dbe57fb09ca607ad59dcf998f3925ef9ac3bb26c/packages/core/src/crawlers/statistics.ts#L560-L596","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nconst shared = new Statistics(); await Promise.all([c1.run(), c2.run()]); // both use shared\n// after\nawait Promise.all([c1.run(), c2.run()]); // each crawler creates its own Statistics internally","handlingStrategy":"validation","validationCode":"if (stats.#logInterval /* or track your own flag */) throw new Error('already capturing');\n// caller-side: keep a Set of instances already started\nconst started = new WeakSet(); if (!started.has(stats)) { started.add(stats); await stats.startCapturing(); }","typeGuard":null,"tryCatchPattern":"try { await stats.startCapturing(); } catch (e) { if (String(e).includes('already capturing')) return; throw e; } // treat as idempotent no-op","preventionTips":["One Statistics instance per crawler","Do not call startCapturing manually when the crawler manages it","Track started instances with WeakSet in shared code"],"tags":["statistics","lifecycle","double-initialization"],"backgroundTag":"double-initialization","analyzedSha":"dbe57fb09ca607ad59dcf998f3925ef9ac3bb26c","analyzedAt":"2026-08-30T22:22:28.328Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}