Repository navigation
fix(agents): classify non-interactive provider errors in managed DCode patch #7422
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
senthilr-nv
merged 16 commits into
NVIDIA:main
from
kagura-chen:fix/7415-non-interactive-error-classification
Jul 24, 2026
Merged
Changes from 4 commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
560c8b3
fix(agents): classify non-interactive provider errors in managed DCod…
kagura-chen 6febe22
fix(agents): walk exception cause chain and read persisted checkpoint…
kagura-chen 2140b33
fix(agents): report managed provider capacity failures
jyaunches 8a34494
merge(main): refresh PR #7422
senthilr-nv e595c4d
test(agents): cover managed diagnostic content redaction
senthilr-nv b78cf19
merge(main): refresh PR #7422
senthilr-nv e30767e
test(dcode): align disclosure fixture with error patch
senthilr-nv bcfa791
merge(main): refresh PR #7422
senthilr-nv d11937f
merge(main): refresh PR #7422
senthilr-nv bccd36a
merge(main): refresh PR #7422
senthilr-nv 23f9a6d
merge(main): refresh PR #7422
senthilr-nv 0fed023
merge(main): refresh PR #7422
senthilr-nv b2d621f
merge(main): refresh PR #7422
senthilr-nv 68ae1ee
merge(main): refresh PR #7422
senthilr-nv 79b2168
merge(main): refresh PR #7422
senthilr-nv 6da4f49
merge(main): refresh PR #7422
senthilr-nv File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,187 @@ | ||
| // SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| import { spawnSync } from "node:child_process"; | ||
| import { afterEach, describe, expect, it } from "vitest"; | ||
| import { | ||
| cleanupPackageFixtures, | ||
| createPackageFixture, | ||
| patchFixture, | ||
| } from "./helpers/langchain-deepagents-code-patch-fixture"; | ||
|
|
||
| afterEach(cleanupPackageFixtures); | ||
|
|
||
| function runPatchedNonInteractive(driver: string) { | ||
| const tempDir = createPackageFixture(); | ||
| patchFixture(tempDir); | ||
|
|
||
| return spawnSync("python3", ["-c", driver], { | ||
| encoding: "utf8", | ||
| env: { | ||
| PATH: process.env.PATH, | ||
| PYTHONPATH: tempDir, | ||
| }, | ||
| timeout: 10_000, | ||
| }); | ||
| } | ||
|
|
||
| const runtimePreamble = ` | ||
| import asyncio | ||
| import logging | ||
| import os | ||
| import sqlite3 | ||
| import tempfile | ||
|
|
||
| from deepagents_code.client import non_interactive as target | ||
|
|
||
| logging.basicConfig(level=logging.WARNING, format="%(message)s") | ||
|
|
||
|
|
||
| class RemoteException(Exception): | ||
| pass | ||
|
|
||
|
|
||
| async def fail(*args, **kwargs): | ||
| del args, kwargs | ||
| raise RemoteException("remote failure token=runtime-secret") | ||
|
|
||
|
|
||
| target._run_non_interactive_impl = fail | ||
| `; | ||
|
|
||
| describe("managed non-interactive error reporting", () => { | ||
| it("reports the provider-capacity error persisted for the failing thread (#7415)", () => { | ||
| const result = runPatchedNonInteractive(` | ||
| ${runtimePreamble} | ||
| handle, db_path = tempfile.mkstemp() | ||
| os.close(handle) | ||
| target._NEMOCLAW_MANAGED_STATE_DB = db_path | ||
| target.generate_thread_id = lambda: "thread-current" | ||
|
|
||
| connection = sqlite3.connect(db_path) | ||
| connection.execute("CREATE TABLE writes (thread_id TEXT, channel TEXT, value BLOB)") | ||
| connection.execute( | ||
| "INSERT INTO writes (thread_id, channel, value) VALUES (?, ?, ?)", | ||
| ( | ||
| "thread-current", | ||
| "__error__", | ||
| sqlite3.Binary( | ||
| b"APIError('ResourceExhausted: Worker local total request limit reached " | ||
| b"(32/32) token=checkpoint-secret request=private model=private-model')" | ||
| ), | ||
| ), | ||
| ) | ||
| connection.commit() | ||
| connection.close() | ||
|
|
||
| exit_code = asyncio.run(target.run_non_interactive("task")) | ||
| assert exit_code == 1 | ||
| os.unlink(db_path) | ||
| `); | ||
|
|
||
| expect(result.status).toBe(0); | ||
| expect(result.stderr).toContain( | ||
| "error_class=ResourceExhausted category=upstream_provider_capacity retryable=true correlation_id=thread-current", | ||
| ); | ||
| expect(result.stdout).toContain( | ||
| "Model request failed: ResourceExhausted (correlation_id=thread-current)", | ||
| ); | ||
| expect(`${result.stdout}\n${result.stderr}`).not.toMatch( | ||
| /runtime-secret|checkpoint-secret|request=private|private-model/, | ||
| ); | ||
| }); | ||
|
|
||
| it("does not use a provider error from another thread (#7415)", () => { | ||
| const result = runPatchedNonInteractive(` | ||
| ${runtimePreamble} | ||
| handle, db_path = tempfile.mkstemp() | ||
| os.close(handle) | ||
| target._NEMOCLAW_MANAGED_STATE_DB = db_path | ||
| target.generate_thread_id = lambda: "thread-current" | ||
|
|
||
| connection = sqlite3.connect(db_path) | ||
| connection.execute("CREATE TABLE writes (thread_id TEXT, channel TEXT, value BLOB)") | ||
| connection.execute( | ||
| "INSERT INTO writes (thread_id, channel, value) VALUES (?, ?, ?)", | ||
| ( | ||
| "thread-other", | ||
| "__error__", | ||
| "APIError('ResourceExhausted: Worker local total request limit reached (32/32)')", | ||
| ), | ||
| ) | ||
| connection.execute( | ||
| "INSERT INTO writes (thread_id, channel, value) VALUES (?, ?, ?)", | ||
| ("thread-current", "__error__", "APIError('MCP connection refused')"), | ||
| ) | ||
| connection.commit() | ||
| connection.close() | ||
|
|
||
| exit_code = asyncio.run(target.run_non_interactive("task")) | ||
| assert exit_code == 1 | ||
| os.unlink(db_path) | ||
| `); | ||
|
|
||
| expect(result.status).toBe(0); | ||
| expect(result.stderr).toContain( | ||
| "error_class=unknown category=unknown retryable=false correlation_id=thread-current", | ||
| ); | ||
| expect(result.stdout).toContain("Unexpected error (correlation_id=thread-current)"); | ||
| expect(`${result.stdout}\n${result.stderr}`).not.toContain("upstream_provider_capacity"); | ||
| }); | ||
|
|
||
| it("keeps MCP, gateway, and policy errors unknown (#7415)", () => { | ||
| const result = runPatchedNonInteractive(` | ||
| ${runtimePreamble} | ||
| handle, db_path = tempfile.mkstemp() | ||
| os.close(handle) | ||
| target._NEMOCLAW_MANAGED_STATE_DB = db_path | ||
|
|
||
| connection = sqlite3.connect(db_path) | ||
| connection.execute("CREATE TABLE writes (thread_id TEXT, channel TEXT, value BLOB)") | ||
| errors = { | ||
| "thread-mcp": "APIError('MCP connection refused')", | ||
| "thread-gateway": "APIError('gateway timeout')", | ||
| "thread-policy": "APIError('policy returned 429')", | ||
| } | ||
| connection.executemany( | ||
| "INSERT INTO writes (thread_id, channel, value) VALUES (?, '__error__', ?)", | ||
| errors.items(), | ||
| ) | ||
| connection.commit() | ||
| connection.close() | ||
|
|
||
| for thread_id in errors: | ||
| target.generate_thread_id = lambda current=thread_id: current | ||
| exit_code = asyncio.run(target.run_non_interactive("task")) | ||
| assert exit_code == 1 | ||
|
|
||
| os.unlink(db_path) | ||
| `); | ||
|
|
||
| expect(result.status).toBe(0); | ||
| expect(result.stderr.match(/error_class=unknown/g)).toHaveLength(3); | ||
| expect(result.stdout.match(/Unexpected error/g)).toHaveLength(3); | ||
| expect(`${result.stdout}\n${result.stderr}`).not.toContain("upstream_provider_capacity"); | ||
| }); | ||
|
|
||
| it("sanitizes unknown failures when checkpoint diagnostics are unavailable (#7415)", () => { | ||
| const result = runPatchedNonInteractive(` | ||
| ${runtimePreamble} | ||
| handle, db_path = tempfile.mkstemp() | ||
| os.close(handle) | ||
| target._NEMOCLAW_MANAGED_STATE_DB = db_path | ||
| target.generate_thread_id = lambda: "thread-no-diagnostics" | ||
|
|
||
| exit_code = asyncio.run(target.run_non_interactive("task")) | ||
| assert exit_code == 1 | ||
| os.unlink(db_path) | ||
| `); | ||
|
|
||
| expect(result.status).toBe(0); | ||
| expect(result.stderr).toContain( | ||
| "error_class=unknown category=unknown retryable=false correlation_id=thread-no-diagnostics", | ||
| ); | ||
| expect(result.stdout).toContain("Unexpected error (correlation_id=thread-no-diagnostics)"); | ||
| expect(`${result.stdout}\n${result.stderr}`).not.toContain("runtime-secret"); | ||
| }); | ||
| }); | ||
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.