fix(memory): skip distillation when the team's only credential is for another provider - #131
Merged
Merged
Conversation
… another provider
The AI availability gate is provider-agnostic; the distillation call site is
provider-specific, and nothing reconciled them.
DistillTeamEventsJob::handle pre-flight-gates on TeamAiAccessChecker::canUseAi
— "does this team have *any* path to AI". DistillTeamEventsAction::distil then
demands one specific provider from config('memory.distillation.model'). A BYOK
team whose only credential is for a different provider (openai, google,
custom_endpoint) therefore clears the gate and then throws
AiAccessUnavailableException from PrismAiGateway::resolveCredential, because it
has no credential for the distillation provider and its plan carries no
platform_llm_fallback.
The guard for this already existed but was too narrow: it matched only the
message 'No available providers in fallback chain', while the real throw arrives
as AiAccessUnavailableException — a purpose-built expected-backpressure type that
BeforeSendFilter and BaseStageJob::isExpectedFailure already treat as such.
Result: the hourly cron pushed these teams into failed_jobs and Sentry on every
run, forever.
Widen the existing catch to recognise the exception type. Skip cleanly and
advance the watermark, exactly as the fallback-chain case already does.
Regression test pins the scenario: distillation configured for one provider, the
gateway throwing AiAccessUnavailableException for it — the action must return a
0-stored result and advance the watermark instead of letting the exception
escape. Verified to fail without the fix.
Sentry: #824, #847, #1035, #1064.
This was referenced Aug 4, 2026
escapeboy
added a commit
that referenced
this pull request
Aug 4, 2026
failed_jobs had never been pruned and had grown to 1916 rows on production, the oldest from 2026-04. Every row is a resolved incident, and the volume drowns the handful that still matter — `queue:failed` shows only the tail, so the table reads as "3 old entries" when it is nearly two thousand. `queue:prune-failed --hours=720` keeps 30 days, which is long enough to investigate a failure after the fact. Verified the schedule entry registers (`15 4 * * *`, next due 19h). Found while validating the current failures rather than assuming they were stale: of the 17 since 2026-07-24, all but one are DistillTeamEventsJob at 01:31 nightly, which stopped after 2026-07-29 (fix #131, 2026-08-01 — six clean nights since, with `memory:distill-events` confirmed still scheduled and due, so the silence is a fix and not a job that quietly stopped running). The remaining one is the browser_task max_steps 400, fixed in #136.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
fix(memory): skip distillation when the team's only credential is for another provider
The AI availability gate is provider-agnostic; the distillation call site is
provider-specific, and nothing reconciled them.
DistillTeamEventsJob::handle pre-flight-gates on TeamAiAccessChecker::canUseAi
— "does this team have any path to AI". DistillTeamEventsAction::distil then
demands one specific provider from config('memory.distillation.model'). A BYOK
team whose only credential is for a different provider (openai, google,
custom_endpoint) therefore clears the gate and then throws
AiAccessUnavailableException from PrismAiGateway::resolveCredential, because it
has no credential for the distillation provider and its plan carries no
platform_llm_fallback.
The guard for this already existed but was too narrow: it matched only the
message 'No available providers in fallback chain', while the real throw arrives
as AiAccessUnavailableException — a purpose-built expected-backpressure type that
BeforeSendFilter and BaseStageJob::isExpectedFailure already treat as such.
Result: the hourly cron pushed these teams into failed_jobs and Sentry on every
run, forever.
Widen the existing catch to recognise the exception type. Skip cleanly and
advance the watermark, exactly as the fallback-chain case already does.
Regression test pins the scenario: distillation configured for one provider, the
gateway throwing AiAccessUnavailableException for it — the action must return a
0-stored result and advance the watermark instead of letting the exception
escape. Verified to fail without the fix.
Sentry: #824, #847, #1035, #1064.