Files
PaperClipAI/ui/src/components/ExecutionBlockerNotice.tsx
T
DottaandPaperclip 9b3fe260ba fix(tasks): surface Codex ChatGPT model rejection (#15299)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for work.
> - Native Runner runs report provider errors and can also save a structured failure result.
> - Codex rejects a model that the user's ChatGPT account cannot use.
> - Master now diagnoses this rejection, but the task's compact run data omits its message. The thread can still call it a generic run failure.
> - Users need the account restriction and a clear step to repair the model selection.
> - This pull request shows the existing diagnosis as “Model unavailable” on the task.

## Linked Issues or Issue Description

**What happened?**

Codex returns HTTP 400 with `invalid_request_error` and the message `The 'gpt-6.1-sol' model is not supported when using Codex with a ChatGPT account.` Master now stores an actionable diagnosis for this rejection. The task thread still labels it “Run failed” and does not receive its error text in compact run data.

**Expected behavior**

Show the account restriction on the task and in the run error. Tell the user to choose a supported model or clear the task's model override before retrying.

**Steps to reproduce**

1. Use a Native Runner Codex agent signed in with a ChatGPT account.
2. Select a model that produces the rejection above and start a task.
3. Let the runner save its generic failed result. Inspect the task's failure marker and recovery notice.

**Additional context**

Refs: #15304. That merged PR diagnoses the provider failure and preserves worker and review recovery rules. This PR adds its task-facing message and guidance without changing that diagnosis or those rules.

Refs: #13134. That PR improves model discovery for ChatGPT accounts. This PR exposes the rejection when a configured model still fails at execution time.

## What Changed

- Return a bounded model rejection message in compact issue-run data.
- Show “Model unavailable” and model-change guidance in the task thread and recovery notice.
- Add database and UI regression tests, including both the original rejection text and master's fixed diagnosis. Document the new failure message.

## Verification

- Focused merged-branch validation: 279 tests passed across activity service, native provider failure observation and PostgreSQL integration, TaskChatThread, and ExecutionBlockerNotice.
- `pnpm -r typecheck`, `pnpm build`, and `pnpm check:token-gates` passed on the merged source tree.
- Greptile reviewed conflict-resolution commit `1db39c4e636a92e2e76ba224b71c6f1e2f556c81` at 5/5 with no findings or inline comments.
- All 55 check runs completed without failure on that commit. The two optional Storybook jobs were skipped. The legacy Snyk status passed. The branch has no merge conflicts.
- UI regression assertion: the task's failure marker says “Model unavailable” and retains the account restriction. It no longer says that this failure happened after a final response.

## Risks

- Provider recognition and recovery are owned by the existing master implementation. This PR exposes only bounded error text for failed runs with `native_provider_model_rejected`.
- Historical runs with the generic `adapter_failed` code are not reclassified. No stored run is rewritten.
- Existing retry and reconciliation gates remain in place. No schema migration or model configuration change is required.

## Model Used

OpenAI Codex, based on GPT-6, with reasoning, code execution, and browser inspection. The exact deployment model ID and context window are not exposed in this session.

## Checklist

- [x] I have included a thinking path that traces from project context to this change
- [x] I have specified the model used (with version and capability details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template
- [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before requesting merge

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-06 08:05:25 -05:00

63 lines
3.1 KiB
TypeScript

import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import type { ExecutionBlocker } from "@paperclipai/shared";
import { agentsApi } from "../api/agents";
import { activityApi } from "../api/activity";
import { queryKeys } from "../lib/queryKeys";
import { Button } from "./ui/button";
import { Link } from "../lib/router";
export function ExecutionBlockerNotice({ companyId, issueId, blocker, onRetried }: {
companyId: string;
issueId: string;
blocker: ExecutionBlocker;
onRetried: () => void;
}) {
const queryClient = useQueryClient();
const { data: runs, error: runsError } = useQuery({
queryKey: queryKeys.issues.runs(issueId),
queryFn: () => activityApi.runsForIssue(issueId),
});
const failedRun = runs?.find(run => run.runId === blocker.runId &&
["failed", "timed_out"].includes(run.status));
const modelRejected = failedRun?.errorCode === "native_provider_model_rejected";
const requiresInspection = blocker.cause === "native_continuation_requires_reconciliation" ||
blocker.cause === "native_session_cleanup_quarantined";
const retry = useMutation({
mutationFn: () => agentsApi.retryFailedRun(blocker.agentId!, blocker.runId!, companyId),
onSuccess: () => {
onRetried();
for (const queryKey of [queryKeys.issues.detail(issueId), queryKeys.issues.runs(issueId),
queryKeys.issues.liveRuns(issueId), queryKeys.issues.activeRun(issueId)]) {
void queryClient.invalidateQueries({ queryKey });
}
},
});
return (
<div role="status" aria-label="Task recovery" className="mx-(--sz-execution-blocker-inline) my-(--sz-execution-blocker-block) flex flex-wrap items-center justify-between execution-blocker-notice border border-border bg-muted text-foreground">
<div className="min-w-0 flex-1 break-words">
<p>{modelRejected ? "Model unavailable." : "Recovery needed."}{blocker.runError ? ` ${blocker.runError}` : ""}</p>
{modelRejected && <p>Choose a supported model or clear the task's model override, then retry.</p>}
<p>{blocker.nextAction}</p>
{Boolean(blocker.savedMessageCount) && (
<p>{blocker.savedMessageCount} saved {blocker.savedMessageCount === 1 ? "message is" : "messages are"} waiting for recovery.</p>
)}
</div>
{blocker.agentId && blocker.runId && (
<Button variant="outline" size="sm" asChild>
<Link to={`/agents/${blocker.agentId}/runs/${blocker.runId}`}>Inspect run</Link>
</Button>
)}
{(!requiresInspection || blocker.canRetry) && blocker.agentId && blocker.runId &&
((blocker.cause === "legacy_execution_requires_reconciliation" && failedRun) || blocker.canContinue || blocker.canRetry) && (
<Button variant="outline" size="sm" disabled={retry.isPending} onClick={() => retry.mutate()}>
{retry.isPending ? "Starting…" : blocker.canContinue ? "Continue" : "Retry"}
</Button>
)}
{retry.isError && (
<p role="alert" className="w-full text-destructive">{retry.error.message}</p>
)}
{runsError && <p role="alert" className="w-full text-destructive">{runsError.message}</p>}
</div>
);
}