Skip to content

fix(runtime): DeepSeekAgent records the failure Claude Code states, like ClaudeAgent - #1225

Merged
aviggiano merged 1 commit into
mainfrom
fix/1084-deepseek-stated-failure
Oct 1, 2026
Merged

aviggiano merged 1 commit into
mainfrom
fix/1084-deepseek-stated-failure

Conversation

@mrthankyou

@mrthankyou mrthankyou commented Sep 30, 2026 •

Copy link
Copy Markdown
Collaborator

A related gap to #1084, found by reading the code; nobody has reported it. #1084 itself is about ClaudeAgent only. Stacked on #1224: this PR's base is fix/1084-claude-stated-failure. Merge #1224 first; this diff then contains only the DeepSeek change.

Testing scope. The failure path was verified locally against the real DeepSeek endpoint with the real Claude Code CLI, using a deliberately invalid key (details under Verification). A successful DeepSeek-backed campaign has not been run locally, because we don't have a DeepSeek key set up. Please run one DeepSeek smoke campaign before relying on this.

Problem

DeepSeekAgent runs Claude Code against DeepSeek's Anthropic-compatible endpoint through its own ClaudeCodeAgent subclass (DeepSeekClaudeCodeAgent). So it has the same gap #1224 fixes for ClaudeAgent: a failed result without an error field is reported only as Claude run failed, and Claude Code's result text, which states the cause (e.g. an auth error from the DeepSeek route), is dropped.

Change

  • agents/deepseek.tsx gets the same output-interpreter hook and generate() override as claude.tsx. The stated text is attached as details.agentStatedFailure; the message and code are unchanged, so nothing Smithers classifies changes.
  • agents/claude.tsx exports its two helpers (claudeResultText, attachStatedFailure) and the GENERIC_CLAUDE_FAILURE constant, so DeepSeek reuses them instead of copying them. openrouter.tsx importing ./codex is the precedent for a cross-adapter import.
  • Boundary gate and doc: deepseek.tsx declares output-interpretation, linked to Concurrent agents race on OAuth token refresh; three immediate retries all re-race and kill the run with an opaque "Claude run failed" #1084.
  • CHANGELOG entry.

The workflow normalizer and run synchronization from #1224 already carry the field through to last_error and the attempt ledger, so nothing else changes.

Verification

  • Real endpoint, invalid key (local): the generated DeepSeekClaudeCodeAgent from this branch, run with Claude Code 2.1.285 and ultrafuzzApiKey set to an invalid value, called generate() once against https://api.deepseek.com/anthropic. DeepSeek rejected the key, so no tokens were spent. Result:

    • code AGENT_CLI_ERROR, message Claude run failed See https://smithers.sh/reference/errors. That's unchanged, and there's no 401 in the message, so Smithers' auth regex does not fire.
    • details.agentStatedFailure: Failed to authenticate. API Error: 401 Authentication Fails, Your api key: ****robe is invalid (request_id: …)

    This confirms that Claude Code reports a DeepSeek route failure only in result, which the fake-CLI test had assumed.

  • New Bun adapter test: it loads the generated DeepSeek adapter and runs it against a fake claude CLI that prints an is_error result and exits 1. The thrown message stays Claude run failed… with code AGENT_CLI_ERROR, details.agentStatedFailure carries a 401-worded statement, and a result without text attaches nothing.

  • Existing tests pass: the DeepSeek and Claude adapter contracts (7/7), the adapter-boundary gate and the init template test.

  • Clean checks: lint, strict lint, Prettier and knip.

  • Not done: a successful DeepSeek-backed campaign (no key), and the full runtime.test.ts locally.

🤖 Generated with Claude Code

RetriggerConfidence Score: 5/5

The PR appears safe to merge based on the available changes.

Summary

The PR preserves Claude Code’s stated failure text for DeepSeek attempts without changing the error message Smithers classifies.

  • Reuses the Claude adapter’s failure-text helpers in the DeepSeek adapter.
  • Updates the adapter-boundary declaration, documentation, changelog, and generated-adapter test.
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[Claude Code result] --> B[DeepSeek output interpreter]
  B --> C[Generic error for Smithers classification]
  B --> D[Stated failure in error details]
  D --> E[Attempt ledger and last_error]
Loading

Reviews (5) · Last reviewed commit: "fix(runtime): DeepSeekAgent records the ..."

@mrthankyou

Copy link
Copy Markdown
Collaborator Author

Priority note for maintainers: this is not a stability fix, so merging it can wait.

Like #1224, this PR makes failures easier to diagnose; it doesn't change whether runs fail. It records the cause Claude Code states next to DeepSeekAgent's generic Claude run failed, but retry, park, auth-disable and stall handling are unchanged.

The branch is up to date with #1224 and main. Its only CI failure is the @grpc/grpc-js advisory gate, which #1239 fixes.

@aviggiano

Copy link
Copy Markdown
Collaborator

Thanks @mrthankyou. This is a clean port of #1224's hook to DeepSeekAgent. Checking the failure path against the real DeepSeek endpoint was especially useful: it confirms Claude Code reports a route failure only in result.

We'll merge this right after #1224. What we checked:

  • Classification: the thrown message and code are unchanged (Claude run failed / AGENT_CLI_ERROR), so retry, quota and auth decisions don't change.
  • Redaction: the recorded text goes through the workflow normalizer. DeepSeek's real 401 wording comes out as Your api key: <redacted>.
  • Tests: the Claude and DeepSeek Bun adapter contracts and the boundary gate pass, and CI is green.

Merge mechanics. #1224 is squash-merged first. We then move this branch's own commits onto main; the resulting tree is identical to the tested head. The squash message is the PR description, because the first commit's line "Not exercised against the real DeepSeek endpoint" is out of date.

What we'll do in a follow-up PR:

  1. Share the hook instead of copying it. Add a small StatedFailureClaudeCodeAgent base class in claude.tsx that holds the field and the two overrides. CompatibleClaudeCodeAgent and DeepSeekClaudeCodeAgent will both extend it, and GENERIC_CLAUDE_FAILURE, claudeResultText and attachStatedFailure go back to being private. DeepSeek deliberately won't extend CompatibleClaudeCodeAgent, because that class's buildCommand forces settingSources = "user".
  2. Pin the reset and the guard with tests. The current tests build a new agent for each call, so they still pass if this.statedFailure = undefined is removed or the GENERIC_CLAUDE_FAILURE guard is dropped. The workflow reuses one adapter per task across attempts, so the follow-up adds a reused-instance test for both adapters: a stated 401, then a stderr-only crash, a limit banner and an explicit error. It will use DeepSeek's observed 401 wording.
  3. Changelog: fold the DeepSeek bullet into the fix(runtime): record the failure Claude Code states instead of only "Claude run failed" #1224 bullet.

One thing we're deliberately not changing: following #1224's rule that the stated text is never classified, DeepSeek's 401/402 wording still isn't classified. A bad key or an empty balance still retries until the identical-failure stall, but the cause now shows in last_error, which is what this PR is for.

Base automatically changed from fix/1084-claude-stated-failure to main October 1, 2026 07:28
…ike ClaudeAgent

DeepSeekAgent runs Claude Code against DeepSeek's Anthropic-compatible
endpoint through its own ClaudeCodeAgent subclass, so it has the same
#1084 gap: a failed result without an `error` field is reported only as
"Claude run failed" and the `result` text is dropped.

It now uses the same output-interpreter hook as the Claude adapter,
sharing that adapter's `claudeResultText` and `attachStatedFailure`
helpers, and carries the text as `details.agentStatedFailure` beside the
unchanged message. The workflow normalizer and run synchronization from
the Claude change already surface it.

Not exercised against the real DeepSeek endpoint: covered by a Bun
adapter test with a fake `claude` CLI only.

Refs #1084

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@aviggiano
aviggiano force-pushed the fix/1084-deepseek-stated-failure branch from 9b5f556 to 4e6c3af Compare October 1, 2026 07:29
@aviggiano
aviggiano merged commit dd691c2 into main Oct 1, 2026
6 of 7 checks passed
@aviggiano
aviggiano deleted the fix/1084-deepseek-stated-failure branch October 1, 2026 07:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants