Skip to content

fix(agent): classify TypeError('NoneType ... not iterable') as retryable provider shape error#33399

Merged
teknium1 merged 2 commits into
mainfrom
hermes/hermes-5bf34d29
May 27, 2026
Merged

fix(agent): classify TypeError('NoneType ... not iterable') as retryable provider shape error#33399
teknium1 merged 2 commits into
mainfrom
hermes/hermes-5bf34d29

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

Salvage of #33136 (@Brixyy) onto current main.

Why

After #33042 made our own Codex consumer structurally immune to NoneType crashes, third-party shims, mocked clients, and any future code path that hasn't migrated can still surface TypeError: 'NoneType' object is not iterable as a wire-shape mismatch. The agent loop's classifier currently treats ALL TypeError as a local programming bug and aborts non-retryable — users on stale Telegram/gateway turns saw bare "Non-retryable error (HTTP None)" with no recovery.

This is a provider/SDK shape mismatch, not a local programming bug. The retry/fallback path should run, not be short-circuited.

Changes

  • agent/conversation_loop.py: extend the is_local_validation_error predicate to exclude TypeErrors whose message matches the NoneType-not-iterable shape (case-insensitive, both "NoneType" and "not iterable" must appear).
  • tests/run_agent/test_jsondecodeerror_retryable.py:
    • update the mirror predicate to match the production check
    • new TestNoneTypeNotIterableIsRetryable class (3 tests: shape match, message variants, unrelated TypeErrors still abort)
    • new TestAgentLoopSourceHasNoneTypeCarveOut enforces the source-level invariant

How this fits the cluster

The defensive-classifier layer that pairs with the structural fixes:

Validation

  • tests/run_agent/test_jsondecodeerror_retryable.py + tests/run_agent/test_31273_402_not_retried.py → 14/14 passing

Why not pure cherry-pick

@Brixyy's original PR was written against the pre-refactor monolithic run_agent.py and added a top-level helper function. Both target functions have since moved to agent/conversation_loop.py:2869, so the salvage applies the equivalent guard inline at the canonical extracted location.

Attribution

Co-authored by @Brixyy. AUTHOR_MAP updated in follow-up commit.

Brixyy and others added 2 commits May 27, 2026 11:20
…ble provider shape error

Salvages the intent of #33136 (@Brixyy) onto current main. The original PR
was written against the pre-refactor monolithic run_agent.py and added a
top-level _is_nonretryable_local_validation_error() helper. Both target
functions have since been extracted to agent/conversation_loop.py:2869,
so the salvage applies the equivalent guard inline at that canonical
location rather than reintroducing the helper.

## Why

After #33042 made our own Codex consumer structurally immune to NoneType
crashes, third-party shims, mocked clients, and any future code path that
hasn't migrated could still surface TypeError: 'NoneType' object is not
iterable as a wire-shape mismatch. The agent loop's classifier currently
treats ALL TypeError as a local programming bug and aborts non-retryable
— users on stale Telegram/gateway turns saw bare "Non-retryable error
(HTTP None)" with no recovery.

This is a provider/SDK shape mismatch, not a local programming bug. The
retry/fallback path should run, not be short-circuited.

## What

agent/conversation_loop.py: extend is_local_validation_error to exclude
TypeErrors whose message matches the NoneType-not-iterable shape (case-
insensitive, both "NoneType" and "not iterable" must appear).

tests/run_agent/test_jsondecodeerror_retryable.py:
- update the mirror predicate to match the production check
- add TestNoneTypeNotIterableIsRetryable class with 3 tests (the basic
  shape, message variants, unrelated TypeErrors still abort)
- add TestAgentLoopSourceHasNoneTypeCarveOut to enforce the source-level
  invariant matches the test mirror

## Validation

tests/run_agent/test_jsondecodeerror_retryable.py +
tests/run_agent/test_31273_402_not_retried.py → 14/14 passing

Co-authored-by: Brixyy <subrtt@gmail.com>
@github-actions

Copy link
Copy Markdown
Contributor

🔎 Lint report: hermes/hermes-5bf34d29 vs origin/main

ruff

Total: 0 on HEAD, 0 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 9507 on HEAD, 9507 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 5006 pre-existing issues carried over.

Diagnostics are surfaced as warnings — this check never fails the build.

@teknium1 teknium1 merged commit 8386f84 into main May 27, 2026
25 checks passed
@teknium1 teknium1 deleted the hermes/hermes-5bf34d29 branch May 27, 2026 18:30
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/agent Core agent loop, run_agent.py, prompt builder labels May 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent loop, run_agent.py, prompt builder P2 Medium — degraded but workaround exists type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants