Skip to content

feat(core): tell the integration when a rate limit is too long to wait for - #658

Open
d-klotz wants to merge 31 commits into
feat/core-rate-limit-requesterfrom
feat/core-rate-limit-message
Open

d-klotz wants to merge 31 commits into
feat/core-rate-limit-requesterfrom
feat/core-rate-limit-message

Conversation

@d-klotz

@d-klotz d-klotz commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

When a provider's rate limit is longer than the in-process cap (5 minutes by default), the integration now records one warning for the user, with the time the limit resets and the provider's links. This is phase 4 of ADR-049 (#655).

Stacked on #656. Merge #656 first. This PR builds on RateLimitError from it, and GitHub retargets this PR to next when #656 merges. It does not depend on #657.

What problem this fixes

  • The user sees nothing. A limit that lasts an hour or a day leaves a run that looks stuck, and no standard message says why, when it ends, or where to read about the limit.
  • Each message that is stored is lost to the next one. The Postgres and MongoDB repositories read integration.messages, a field the schema does not have, so every updateIntegrationMessages call replaced the array with a one-item array instead of appending. This is fixed here because the warning needs it.

What this PR does

  • A RATE_LIMITED delegate event, on the same path as CREDENTIAL_INVALIDATED: Requester → Module → IntegrationBase. The Requester sends it when it throws a RateLimitError whose wait is longer than the in-process cap (maxInProcessWaitMs, 5 minutes by default). It does not send it for a wait that it slept through, for a shorter wait that throws only because the invocation ends soon, or for a limit with no time.

  • One warning on the integration, in the stored warnings:

    {
        title: 'Rate limit reached',
        message: 'The hubspot API rate limit was reached and resets at 2026-09-28 23:00 UTC.',
        timestamp, code: 'RATE_LIMITED', module, reason,
        retryAt,                              // ISO UTC; the UI shows local time
        actions: [
            { type: 'RETRY_WHEN_READY' },
            { type: 'LINK', label, url },     // from the module's userHints[reason].links
        ],
    }
  • Client-safe text. The message is fixed text plus the reset time. The error's message, URL, body and headers never reach it: a FetchError message can echo request data, and this reaches end users.

  • No status change. A rate limit is not an error, so the integration keeps its status.

  • One warning per limit, not one per worker. Twenty workers can meet the same limit at once. A warning for the same module with a retryAt within 60 s of the last one is skipped. The check reads the stored warnings (a new findIntegrationMessages on the repository), not IntegrationBase.messages, which is per instance.

  • updateIntegrationMessages takes a message item object as well as the positional form (title, body, timestamp). The use case builds the item; the repositories only store it. Existing callers are unchanged.

  • Each message type keeps its newest 50 items. Messages now append, and testAuth and validateConfig write one on every call, so the use case sets the limit and the repository removes the oldest items.

  • A payload without a module name or a valid retryAt is skipped, and a failed write logs one WARN frigg.integrations.rate_limit_message_record_failed. A failed propagation in the Module logs WARN rate_limit_propagation_failed.

  • A module that throws its own RateLimitError (one built on a vendor SDK, not on the Requester) calls await this._notifyRateLimited(error) first, so its users get the warning too.

Known gap: the warning does not show on Postgres and MongoDB yet

The warning is saved, but the read methods of the Postgres and MongoDB repositories map messages from the same field that does not exist. The messages of a hydrated integration and of the integration DTO are therefore empty there. This is true of every message today, the existing authentication error too. It is not new, and this PR does not change it: mapping the four columns into messages changes the DTO for every app, and deserves its own small PR. DocumentDB keeps a real messages field and is not affected. Until that change lands, the UI signal for a wait is Process.context.rateLimit from #657.

Caveats

  • Duplicates and lost appends under a race. The 60 s check is read, then write, and updateIntegrationMessages is itself read-modify-write. Workers that report in the same instant can write a few copies, and a concurrent append can be lost.
  • The repository method now needs the keepLast option. Only the two use cases call it; a direct caller without the option gets a TypeError.
  • Two database round trips before the throw. The notification is awaited, because an un-awaited write can be dropped when the Lambda returns. It adds a read and a write to a call that is about to fail anyway.
  • Run against real Postgres only. The MongoDB and DocumentDB repositories are tested with mocked Prisma and raw commands.

Not in this PR

Pacing, the module policies (api-module-library), and the fix for the read path above.

Test plan

  • core: npm test in packages/core — 2,844 pass (with feat(core): honour Retry-After and let API modules declare rate limits #656 merged in); the 63 failures (DocumentDB, Prisma decryption, Mongo memory server) are the same 63 that fail on origin/next in this environment
  • new suites: Requester delegate, Module, RecordRateLimitMessage, the IntegrationBase receive and record paths, message-item-shared, and the message tests of the three repositories (append, object item, findIntegrationMessages)
  • type declarations checked with tsc; Integration.messages was declared as empty tuples and is now IntegrationMessage[]
  • local end-to-end run on real Postgres (frigg-local-test harness: real Postgres, LocalStack SQS and Scheduler, a real QueueWorker from createQueueWorker, and an integration whose API module calls a local fake provider): a daily 403 writes one RATE_LIMITED warning with the reset time and the provider link, and none of the provider's text; a second limit within 60 s is deduped; a 3 s wait writes no warning; 55 appends through IntegrationBase keep the newest 50
  • eslint: 0 errors. Warnings are on lines that already existed, except 2 no-unused-vars on the parameters of the new abstract findIntegrationMessages, like the 22 stubs around it
  • CI build (22.x) fails on feat(core): add queryMappings to IntegrationMappingRepository #651, feat(core): tell queue handlers their last delivery attempt #653 and feat(core): structured logging (ADR-048) #654 too (frigg-cli, devtools infrastructure and management-ui suites, and a missing libcrypto.so.1.1 for the Mongo memory server), so it does not gate this PR

Release notes

  • Upgrade @friggframework/core. No redeploy step beyond that.
  • A module that throws its own RateLimitError should call await this._notifyRateLimited(error) before it throws. The Salesforce policy PR in api-module-library does.

🤖 Generated with Claude Code

📦 Published PR as canary version: 2.0.0--canary.658.164d92b.0

✨ Test out this PR locally via:

npm install @friggframework/admin-scripts@2.0.0--canary.658.164d92b.0
npm install @friggframework/core@2.0.0--canary.658.164d92b.0
npm install @friggframework/devtools@2.0.0--canary.658.164d92b.0
npm install @friggframework/eslint-config@2.0.0--canary.658.164d92b.0
npm install @friggframework/prettier-config@2.0.0--canary.658.164d92b.0
npm install @friggframework/schemas@2.0.0--canary.658.164d92b.0
npm install @friggframework/serverless-plugin@2.0.0--canary.658.164d92b.0
npm install @friggframework/test@2.0.0--canary.658.164d92b.0
npm install @friggframework/ui@2.0.0--canary.658.164d92b.0
# or 
yarn add @friggframework/admin-scripts@2.0.0--canary.658.164d92b.0
yarn add @friggframework/core@2.0.0--canary.658.164d92b.0
yarn add @friggframework/devtools@2.0.0--canary.658.164d92b.0
yarn add @friggframework/eslint-config@2.0.0--canary.658.164d92b.0
yarn add @friggframework/prettier-config@2.0.0--canary.658.164d92b.0
yarn add @friggframework/schemas@2.0.0--canary.658.164d92b.0
yarn add @friggframework/serverless-plugin@2.0.0--canary.658.164d92b.0
yarn add @friggframework/test@2.0.0--canary.658.164d92b.0
yarn add @friggframework/ui@2.0.0--canary.658.164d92b.0

d-klotz and others added 13 commits September 28, 2026 20:37
The rate-limit block pins Math.random to 0 in beforeEach so jitter is zero.
source-map-support parses a file's source map on the first stack read, and
the sorter in source-map 0.6 picks its pivot with Math.random. With a
constant 0 the pivot is always the first element, so on sorted input the
recursion is as deep as the file has mappings.

The first stack read in this file is jest's toThrow in "fails at
construction for a parser that does not exist". Any growth of
requester.js (21 lines are enough) or of this file (about 160 lines)
pushes that parse past the call stack limit and the test fails with
"RangeError: Maximum call stack size exceeded", far from its cause.

A beforeAll now reads one stack from the same path (test file, Requester
constructor, policy reader) while Math.random is still real, so the parse
is cached before the mock exists. Padding requester.js by 400 lines keeps
the file green with this in place.

Refs ADR-049.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The Requester throws a RateLimitError when a wait does not fit the in-process
budget, and nothing above it hears about it. The end user sees an integration
that silently stops syncing. The Requester now declares RATE_LIMITED next to
INVALID_AUTH and notifies its delegate with the error, once, right before it
throws. The Module and IntegrationBase turn that into a message in later
commits.

The notification is best effort. A failed one writes a WARN
(rate_limit_notify_failed with statusCode and error) and the caller still
gets the RateLimitError, because the throw is what makes the queue worker
defer the message. It is awaited, so a Lambda that returns right after the
throw has written the message already. Waits that fit, bare 429s on the
ladder, 5xx and errors that classify does not recognise never notify.

The tests live in a sibling file, like requester.logger.test.js, and do not
mock Math.random.

Refs ADR-049.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The Requester now notifies its delegate when a rate-limit wait is too long,
and the Module is that delegate. It passes the notification up as
RATE_LIMITED with the reason, the reset time, the policy name, the status
code and the links the module declares for that reason in
`static rateLimit.userHints`. Without hints the links are empty.

The payload has no message, url, body or headers of the error: a FetchError
message can echo request data and the integration shows this to end users.

Best effort, like markCredentialsInvalid. A failing parent delegate writes
one ERROR (rate_limited_propagation_failed) and never replaces the
RateLimitError that the caller receives.

Refs ADR-049.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The Postgres and Mongo repositories read the stored messages from
`integration.messages`. The Integration model has no such field: the messages
live in the errors, warnings, info and logs columns. The read was always
empty, so every updateIntegrationMessages call replaced the column with a
one-item array. Only the last message of each type survived.

The repositories now read `integration[messageType]`, the column they write.
Two consecutive calls leave two items, items stored before stay, and the
other columns are not touched. The DocumentDB adapter builds the list from
its stored copies and appended correctly already; its tests are added so it
stays that way.

Refs ADR-049.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Messages had a fixed shape: title, message and timestamp. A message that a
client acts on needs more, for example a code and a list of actions. The
use case and the three repositories now accept one item object as well as the
positional form (title, body, timestamp), and they store the keys of the item
as they are. The positional form is unchanged, so every existing caller works
without a change.

The normalisation lives in one shared helper, like config-patch-shared, so the
Postgres, Mongo and DocumentDB adapters cannot drift. DocumentDB still writes
a null title when there is none. The in-memory test repository uses the same
helper.

Refs ADR-049.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A caller that must know what an integration already shows to its users, for
example to write a message only once, cannot ask for it. The record from
findIntegrationById has a `messages` field, but the Postgres and Mongo
adapters map it from a column that does not exist, so it is always
undefined there, and the in-memory copy on IntegrationBase is per instance.

findIntegrationMessages(integrationId, messageType) reads the stored items of
one type from the same column updateIntegrationMessages writes. It selects
only that column on Postgres and Mongo. The record mapping is not changed:
the existing tests pin it, and changing it would change what clients receive.

Refs ADR-049.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
When a module reports a rate limit that is too long to wait for, the
integration needs one message for its users. RecordRateLimitMessage writes
a warning with a fixed title and text, the reset time in UTC rounded up to
the minute, and the code, module, reason, retryAt and actions a client acts
on: RETRY_WHEN_READY plus one LINK per link the module declares.

The text is fixed and only known payload fields are read. A FetchError
message can echo the request, and this message reaches end users.

Many workers hit one limit together and each reports it, so a warning for
the same module whose reset time is within 60 seconds is not written again.
The check reads the stored warnings through findIntegrationMessages: the
in-memory messages of an instance are empty on Postgres and differ between
workers. The check and the write are two steps, so workers that report at
the same instant can still write a few duplicates.

It never changes the integration status and lets read and write failures
reach the caller, which decides what a lost warning is worth.

Refs ADR-049.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…-message

Takes the review fixes of the Requester (7302a64). The throw of the
RateLimitError moved into _throttleRetry, so the RATE_LIMITED notification
is sent from there, after the error is built and before it is thrown. A
limit that classify names with no time now ends as a FetchError flagged
isRateLimited, not as a RateLimitError, and does not notify; a test covers
that.

The Requester tests no longer replace Math.random, so the source-map warm-up
added earlier on this branch has no cause left and is dropped:
requester.test.js is the upstream file.

Refs ADR-049.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
IntegrationBase now handles RATE_LIMITED, the notification that travels from
the Requester through the Module. It writes one WARN (rate_limited with the
module, the reason and the reset time) and records the user-facing message
with RecordRateLimitMessage.

The integration status does not change. A rate limit is not an ERROR: the
provider asked us to wait, the credentials are fine, and an ERROR status
makes the queue worker drop every webhook until someone re-authorizes.

Recording is best effort, like the credential rejection. A failed read or
write logs one ERROR (rate_limit_message_record_failed) and returns, so the
Requester still throws the RateLimitError that lets the worker defer the
message. An end-to-end test drives a real Requester, Module and
IntegrationBase over an in-memory repository: one warning per limit, with
the links of the module and nothing from the request.

Refs ADR-049.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Declares DLGT_RATE_LIMITED on the Requester, and an IntegrationMessage type
for the items in the stored messages, with the fields a rate-limit warning
adds (code, module, reason, retryAt, actions). The declared type of
Integration.messages was a list of empty tuples, which no item could
satisfy.

Refs ADR-049.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… error

A module that drives another client, as jsforce does, throws RateLimitError
itself, and the Requester notifies only for the errors it throws. Such a
module calls _notifyRateLimited before its own throw. The tests pin that
call: it reaches the delegate with the error, and it resolves with one WARN
when the notification fails.

Refs ADR-049.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Describes the RATE_LIMITED path (Requester, Module, IntegrationBase), the
stored warning with its code, retryAt and actions, the one-per-limit rule,
and that the status does not change. userHints feeds the LINK actions. The
example of a module that throws RateLimitError itself now calls
_notifyRateLimited, because the Requester notifies only for its own throws.
The logging guide lists the four new records.

CLAUDE.md also records that the messages of the record from
findIntegrationById are not filled on Postgres and Mongo, so
findIntegrationMessages is the read for stored messages.

Refs ADR-049.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The commit that added the RATE_LIMITED branch also added a return to the end
of the CREDENTIAL_VALIDATED branch. The branches test different strings, so
the return changes nothing, and it touched a line that this work does not
need to touch.

Refs ADR-049.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@d-klotz d-klotz added release Create a release when this pr is merged prerelease This change is available in a prerelease. labels Sep 29, 2026

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Code review skipped — your organization's extra usage balance is too low to start another review, so this review was not started.

An organization admin can add extra usage credits at claude.ai/admin-settings/usage.

Once credits are available, reopen this pull request to trigger a review.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-29T00:20:07.497554Z a5177aa PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a5177aa26a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +78 to +79
/** Notified with the `RateLimitError` right before a wait that is too long throws. */
DLGT_RATE_LIMITED: string;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Declare the documented rate-limit notifier

TypeScript API modules following the new example in docs/reference/api-module-definition-and-functions.md cannot call this._notifyRateLimited(error): the runtime Requester implements the method, but neither the exported Requester declaration nor IFriggRequester declares it, so consumers receive TS2339. Add a _notifyRateLimited(rateLimitError: RateLimitError): Promise<void> signature to both public types.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 93c8e07. _notifyRateLimited(rateLimitError: RateLimitError): Promise<void> is now declared on Requester and on IFriggRequester. Checked with a tsc --strict consumer that subclasses Requester and calls it: TS2339 before, clean after.

d-klotz and others added 10 commits September 28, 2026 21:25
A TypeScript module that follows the documented pattern calls
`this._notifyRateLimited(error)` before it throws its own RateLimitError.
Neither `Requester` nor `IFriggRequester` declared the method, so the call
failed with TS2339.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
makeRequester now passes random: () => 0, as requester.test.js does, so the
jitter of a slept wait no longer reads Math.random.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Removes the explanatory comment lines this change added to IntegrationBase,
Module, the Requester, RecordRateLimitMessage, the message item helper and
both type declarations. The pre-existing receiveNotification text is back as
it was. The rationale lives in the ADR-049 docs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…cess cap

The owner decided that the rate-limit warning is for a limit the Requester
cannot sleep through at all. _notifyRateLimited now returns without a
notification when the wait of the error (waitMs, else retryAt minus now) is
at most maxInProcessWaitMs of the requester's policy (default 5 minutes).

Both paths go through it: the Requester's own throw, and a module that throws
its own RateLimitError. A 30 s concurrency limit, or a short wait that only
misses the invocation deadline, now writes no warning; an hour-long daily
limit still does. The docs say so.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Module.reportRateLimit read static rateLimit.userHints directly, so it used
hints the Requester's own policy ignores (a rateLimit or userHints that is not
a plain object). It now reads the links from readRateLimitPolicy(apiClass),
the validated and memoized policy the Requester uses.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ropagation_failed

The request still throws its RateLimitError when the Module cannot pass the
report on, so the failure is handled and the system continues: WARN, not
ERROR. The event name now follows <logger>.rate_limit_<step>_failed, like the
Requester's rate_limit_notify_failed. LOGGING.md lists the new name and level.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
utcMinuteAfter returned a string, so it is formatResetTime now. RATE_LIMITED
in record-rate-limit-message.js is the message code, not the delegate event,
so it is RATE_LIMITED_MESSAGE_CODE. isItem checked for a non-array object,
the check integration-mapping-query.js already calls isPlainObject.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Postgres, MongoDB and DocumentDB each spelled out
Array.isArray(record[type]) ? record[type] : [] twice, once to append and once
to find. messagesOfType in message-item-shared.js is now that one check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Postgres and MongoDB message tests carried the same copy of a Prisma
double that keeps one integration row. withMessageRow in tests/doubles is now
that double; each test file only names its repository and id.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Repositories are pure data access (packages/core/CLAUDE.md, golden rule 3),
yet the three of them and the test double each decided between the
positional form and an item. UpdateIntegrationMessages.execute now builds the
one item and still accepts both forms from its callers.
updateIntegrationMessages on the repository takes (integrationId,
messageType, item) only. RecordRateLimitMessage already passes an item.

toMessageItem moved from message-item-shared.js into the use case, its only
caller; its tests moved to the use case tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
d-klotz and others added 8 commits September 29, 2026 17:51
Messages now append, so testAuth (one error per failing module on every
GET /test-auth), validateConfig (one warning per missing key on every
onUpdate) and the rate-limit warnings would grow the stored arrays without a
bound on Postgres and MongoDB. DocumentDB already appended without one.

The use case sets the limit: MAX_MESSAGES_PER_TYPE = 50 in
update-integration-messages.js, which RecordRateLimitMessage also uses. The
repository method takes it as updateIntegrationMessages(integrationId,
messageType, item, { keepLast }) and drops the oldest items of that type
after the append: a slice in the read-modify-write of Postgres, MongoDB,
DocumentDB (both stored copies) and the test double.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No production code read the boolean the use case returned. It now resolves
to undefined, and its tests assert on the writes the repository received.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… reset time

A RATE_LIMITED payload without a module name stored "The undefined API rate
limit..."; one without a valid retryAt threw a RangeError after the database
read. RecordRateLimitMessage now returns before any read or write when
moduleName is not a non-empty string or retryAt is not a valid time, and also
when there is no payload. A Date, an ISO string and epoch milliseconds still
work.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Module always sets moduleName in the payload (definition.moduleName is
required), so the object?.moduleName ?? notifier?.name fallback in
IntegrationBase.receiveNotification never ran for a Module. A payload without
a module name now reaches RecordRateLimitMessage unchanged, which writes
nothing for it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Use cases in core log: DeleteIntegrationForUser writes WARN records through
a module-level getLogger('frigg.integrations'). RecordRateLimitMessage now
does the same. A failed read or write of the warning, or a missing
integration, writes one WARN (frigg.integrations.rate_limit_message_record_failed,
with integrationId, moduleName and error) and resolves, as the plan asks: "a
read failure logs and returns". The request still throws its RateLimitError.

The failure is handled and the system continues, so the record is a WARN,
not the ERROR IntegrationBase wrote. IntegrationBase.receiveNotification now
calls the use case directly; its _recordRateLimitMessage wrapper is gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… guide

The item form of updateIntegrationMessages, findIntegrationMessages and the
limit of the newest 50 messages per type move from packages/core/CLAUDE.md
into a short "Integration Messages" section of INTEGRATION-PATTERNS.md, next
to the rate-limit text; CLAUDE.md keeps a one-line pointer. The rate-limit
docs now say that the stored warnings keep their newest 50 items.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ws its own error

The API module reference now says that this._notifyRateLimited(error) never
rejects, that it writes the warning only for a wait longer than
maxInProcessWaitMs, and that a module which must also run on an older core
calls this._notifyRateLimited?.(error).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
# Conflicts:
#	docs/reference/api-module-definition-and-functions.md

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Code review skipped — your organization has no extra usage available to pay for this review.

If your organization's extra usage balance is empty, an organization admin can add extra usage credits at claude.ai/admin-settings/usage. If its monthly spend limit was reached, an admin can raise it on the same page. If neither applies, contact Anthropic support.

Once extra usage is available, comment @claude review on this pull request, or push a new commit, to trigger a review.

@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

prerelease This change is available in a prerelease. release Create a release when this pr is merged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant