{"record":{"id":"7042a059a7da3a19","repo":"apify/crawlee","slug":"cannot-honour-a-pacing-signal-scoped-to-scope","errorCode":null,"errorMessage":"Cannot honour a pacing signal scoped to \"${scope}\": this manager groups requests by \"${this.#throttleBy}\" and holds one request queue per group, so that is the widest scope it can hold back at once. Applying the signal anyway would leave part of what it covers unpaced. Set `throttleBy: \"registrableDomain\"` to group requests that way. / This manager only understands the scopes \"hostname\" and \"registrableDomain\"; pace by \"${scope}\" with a request manager that groups requests by it.","messagePattern":"Cannot honour a pacing signal scoped to \"(.+?)\": this manager groups requests by \"(.+?)\" and holds one request queue per group, so that is the widest scope it can hold back at once\\. Applying the signal anyway would leave part of what it covers unpaced\\. Set `throttleBy: \"registrableDomain\"` to group requests that way\\. / This manager only understands the scopes \"hostname\" and \"registrableDomain\"; pace by \"(.+?)\" with a request manager that groups requests by it\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/core/src/storages/throttling_request_manager.ts","lineNumber":672,"sourceCode":"     * throw rather than under-apply, because pacing one group would leave the rest of what it covers unpaced.\n     */\n    #assertScopeHonourable(scope: string | undefined): void {\n        // No scope means the reporter cannot tell how far the signal reaches, so our own grouping is as good\n        // an answer as there is.\n        if (scope === undefined || scope === this.#throttleBy) {\n            return;\n        }\n\n        if (scope === 'hostname' && this.#throttleBy === 'registrableDomain') {\n            // Only debug: grouping by registrable domain deliberately paces whole sites, subdomains included.\n            this.log.debug(\n                `Applying a pacing signal scoped to \"hostname\" across the whole registrable domain, because that ` +\n                    `is how this manager groups requests (\\`throttleBy\\`). Sibling subdomains are paced with it.`,\n            );\n            return;\n        }\n\n        throw new Error(\n            `Cannot honour a pacing signal scoped to \"${scope}\": this manager groups requests by ` +\n                `\"${this.#throttleBy}\" and holds one request queue per group, so that is the widest scope it can ` +\n                `hold back at once. Applying the signal anyway would leave part of what it covers unpaced. ` +\n                (scope === 'registrableDomain'\n                    ? 'Set `throttleBy: \"registrableDomain\"` to group requests that way.'\n                    : 'This manager only understands the scopes \"hostname\" and \"registrableDomain\"; pace by ' +\n                      `\"${scope}\" with a request manager that groups requests by it.`),\n        );\n    }\n\n    /** Puts the URL's domain into backoff after a refusal, doubling from `baseDelaySecs` if it keeps refusing. */\n    #recordRateLimit(url: string, waitMs?: number): boolean {\n        const state = this.#getDomainState(url);\n        if (!state) {\n            return false;\n        }\n\n        const now = Date.now();","sourceCodeStart":654,"sourceCodeEnd":690,"githubUrl":"https://github.com/apify/crawlee/blob/dbe57fb09ca607ad59dcf998f3925ef9ac3bb26c/packages/core/src/storages/throttling_request_manager.ts#L654-L690","documentation":"#assertScopeHonourable rejects pacing signals whose scope is wider than the manager's grouping (`throttleBy`) or of an unknown kind. A signal can only be honoured if the manager's request groups fully cover what the signal scopes; otherwise part of the signal's coverage would run unpaced.","triggerScenarios":"Calling recordPacingSignal with scope 'registrableDomain' while throttleBy is 'hostname', or with any scope other than 'hostname'/'registrableDomain' (the latter yields the second message variant).","commonSituations":"Relaying robots.txt crawl-delay scoped to a registrable domain into a hostname-grouped manager; typo'd scope strings like 'domain' or 'global'; swapping manager implementations without updating pacing code.","solutions":["Construct the manager with `throttleBy: 'registrableDomain'` if you need domain-wide signals","Send the signal with a scope matching the manager's throttleBy (e.g. 'hostname')","Use only the supported scopes 'hostname' and 'registrableDomain'","Split the signal per-hostname and record it individually on hostname-grouped managers"],"exampleFix":"// before\nconst mgr = new ThrottlingRequestManager({ throttleBy: 'hostname' });\nmgr.recordPacingSignal({ scope: 'registrableDomain', minDelaySecs: 3 }); // throws\n// after\nconst mgr = new ThrottlingRequestManager({ throttleBy: 'registrableDomain' });\nmgr.recordPacingSignal({ scope: 'registrableDomain', minDelaySecs: 3 });","handlingStrategy":"validation","validationCode":"const SUPPORTED = ['hostname', 'registrableDomain'];\nfunction canRecord(manager, signal) {\n  if (!SUPPORTED.includes(signal.scope)) return false;\n  return signal.scope !== 'registrableDomain' || manager.throttleBy === 'registrableDomain';\n}","typeGuard":"const isRecordableScope = (s) => s === 'hostname' || s === 'registrableDomain';","tryCatchPattern":"try {\n  manager.recordPacingSignal(signal);\n} catch (err) {\n  if (/Cannot honour a pacing signal scoped/.test(String(err))) {\n    logger.warning('Pacing signal scope mismatch', { scope: signal.scope });\n  } else throw err;\n}","preventionTips":["Match signal scope to the manager's throttleBy setting","Only use the documented scopes 'hostname' and 'registrableDomain'","Prefer throttleBy: 'registrableDomain' when consuming robots.txt crawl-delays"],"tags":["throttling","configuration","scope"],"backgroundTag":"pacing-scope-unhonourable","analyzedSha":"dbe57fb09ca607ad59dcf998f3925ef9ac3bb26c","analyzedAt":"2026-08-30T22:22:28.328Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}