fix(ai): handle ChatGPT Business workspace usage limit errors

Sign in with ChatGPT on a ChatGPT Business plan can fail with
subscription_sharing_workplace_individual_usage_limit_exceeded. Only the
workspace admin can raise this limit, so retrying never helps.

- Mark the error as non-retryable
- Add a hint to the error message telling the user to ask their
  workspace admin to increase the limit
This commit is contained in:
David Brailovsky 2026-10-21 10:11:54 +02:00
parent e5fe9777b9
commit cedefc5326
4 changed files with 20 additions and 3 deletions

View file

@ -12,6 +12,7 @@
- Added `AI_TELEMETRY_SCHEMA` and opt-in `pi.ai.request`/`pi.ai.poll` tracing around Models requests, with content-free first-event, block-size, and final usage attributes, plus duration, token, and USD cost metrics through `telemetry.metrics`.
- Failed model request spans report `provider_error` status with up to 500 error-message characters; cancelled requests keep `ok` status with `stop: "aborted"`.
- Added `MiniMax-M3.1-Flash` to the `minimax` and `minimax-cn` providers, with thinking levels low through max sent as `output_config.effort`. Thinking cannot be turned off for this model. New MiniMax models from models.dev now appear without a manual allowlist entry; only the older M2, M2.1, M2.5, and M2.5-highspeed models stay hidden ([#11121](https://github.com/earendil-works/pi/issues/11121))
- Sign in with ChatGPT: ChatGPT Business workspace usage-limit errors are not retried and tell the user to ask their workspace admin to increase the limit.
### Changed

View file

@ -37,6 +37,8 @@ const OPENAI_TOOL_CALL_PROVIDERS = new Set(["openai", "openai-codex", "opencode"
// OpenAI Responses rejects max_output_tokens below 16: https://github.com/earendil-works/pi/issues/6265
const OPENAI_RESPONSES_MIN_OUTPUT_TOKENS = 16;
const CHATGPT_USAGE_URL = "https://chatgpt.com/settings/usage";
const CHATGPT_WORKSPACE_LIMIT_HINT =
"You've reached your ChatGPT workspace's usage limit. Ask your workspace admin to increase it at admin.openai.com → Workspace → Usage limits.";
// Fixed reasoning.effort while configuration_update items carry the thinking level.
const CONFIGURATION_UPDATE_REQUEST_EFFORT = "medium";
@ -258,9 +260,14 @@ export const stream: StreamFunction<"openai-responses", OpenAIResponsesOptions>
`${model.provider === "openai" ? "OpenAI" : model.provider} API error`,
);
// Sign in with ChatGPT shares the subscription's usage limit with other apps.
output.errorMessage = errorMessage.includes("subscription_sharing_usage_limit_exceeded")
? `${errorMessage}\nCheck your ChatGPT usage: ${CHATGPT_USAGE_URL}`
: errorMessage;
// On ChatGPT Business plans, the workspace admin also sets a per-user limit.
if (errorMessage.includes("subscription_sharing_usage_limit_exceeded")) {
output.errorMessage = `${errorMessage}\nCheck your ChatGPT usage: ${CHATGPT_USAGE_URL}`;
} else if (errorMessage.includes("subscription_sharing_workplace_individual_usage_limit_exceeded")) {
output.errorMessage = `${errorMessage}\n${CHATGPT_WORKSPACE_LIMIT_HINT}`;
} else {
output.errorMessage = errorMessage;
}
stream.push({ type: "error", reason: output.stopReason, error: output });
stream.end();
}

View file

@ -25,6 +25,9 @@ const NON_RETRYABLE_PROVIDER_LIMIT_ERROR_PATTERN = buildProviderErrorPattern([
// Sign in with ChatGPT: the subscription's shared usage limit, which resets
// after hours rather than seconds.
"subscription_sharing_usage_limit_exceeded",
// Sign in with ChatGPT on a ChatGPT Business plan: a usage limit set by the
// workspace admin. Only the admin can raise it.
"subscription_sharing_workplace_individual_usage_limit_exceeded",
]);
const RETRYABLE_PROVIDER_ERROR_PATTERN = buildProviderErrorPattern([

View file

@ -126,6 +126,12 @@ describe("provider retry classification", () => {
expect(isRetryableAssistantError(fauxAssistantMessage("", { stopReason: "error", errorMessage }))).toBe(false);
});
it("keeps the ChatGPT Business workspace usage limit non-retryable", () => {
const errorMessage =
'OpenAI API error (429): {"code":"subscription_sharing_workplace_individual_usage_limit_exceeded","message":"Usage limit reached."}';
expect(isRetryableAssistantError(fauxAssistantMessage("", { stopReason: "error", errorMessage }))).toBe(false);
});
it.each([
"subscription_sharing_usage_unavailable: Usage cannot be checked.",
"subscription_sharing_user_unavailable: User cannot be loaded.",