Skip to content

fix(agent-server): reject a create whose agent tool module cannot be imported - #5958

Merged
enyst merged 1 commit into
mainfrom
claude/umbrella-bug-fixes-tgz9ai-tool-module-unimportable
Oct 17, 2026
Merged

enyst merged 1 commit into
mainfrom
claude/umbrella-bug-fixes-tgz9ai-tool-module-unimportable

Conversation

@enyst

@enyst enyst commented Oct 14, 2026 •

Copy link
Copy Markdown
Member

HUMAN:
This PR proposes a fix for a bug uncovered during the first full agentic run of verify-agent-server.


AGENT:

I'm an AI agent (Claude Code) helping Engel Nyst (@enyst) with project maintenance.

Requested by Engel · project thread

Decision needed

Where the failure surfaces is an API-contract choice. This PR rejects the create with 422 (detail names the tool and the module), and creates no conversation. Reason: the agent can never be built on this server, the caller is still at the point where it can fix its deployment or agent, and it matches the existing 422 for client-tool registration errors on the same route. It is also consistent with #5771, which keeps a fresh conversation strict about unregistered tools and only relaxes resume.
Alternatives a maintainer may prefer: (a) start without the tool and log a warning (what the old "fail gracefully" comment suggested; the conversation works but silently lacks a tool the caller asked for), or (b) keep 201 and record a conversation error / answer 4xx on each message (the caller learns later and is left with an unusable conversation).

Why

A client that registers a custom tool the agent server cannot import (its module is not on the server's sys.path) and puts it in the agent got 201 on create, then an unhandled 500 (KeyError: ToolDefinition '<name>' is not registered) on the first and every later message.

Summary

  • _create_conversation remembers which tool_module_qualnames modules failed to import; once the agent is launched, if the agent uses one of those tools and it is not otherwise resolvable, it raises UnavailableAgentTool, which the router maps to 422. A module whose tool the agent does not use is still only a warning.
  • Map: F33.custom-tool-module-unimportable drops its known-bug marker and now asserts the 422; the F33 gotcha and the F27 custom-tool-modules prose are updated.

Issue Number

Fixes #5940

How to Test

  • uv run pytest tests/agent_server/test_conversation_service.py tests/agent_server/test_conversation_router.py: 233 passed. New: test_agent_tool_from_unimportable_module_rejects_create (service: raises, no conversation persisted; an unused unimportable module still starts) and test_start_conversation_unavailable_agent_tool_returns_422 (router).
  • uv run pytest -n 8 tests/agent_server: 2596 passed, 1 failed (canvas_extensions/test_canvas_extension_backend.py::test_exited_leader_descendant_does_not_block_recovery[status], which fails the same way on main in this environment).
  • Map proof (private AGENT_SERVER_VERIFY_HOME):
    • control-agent-server map run --file F33 --only F33.custom-tool-module-unimportable --fresh --repeat 2 --checkout <main at 6e7a853>: xfail twice ({"create": 201, "message": 500}).
    • Same command from this branch: xpass twice ({"create": 422}).
    • After dropping the marker: map check ok; map run --file F33 --only F33.custom-tool-module-unimportable --fresh: pass (3/3, including F33.custom-tool-module); map run --file F27 --only F27.custom-tool-modules --fresh: pass.
  • uv run pre-commit run --files <changed files>: all hooks pass.

Video/Screenshots

n/a

Type

  • Bug fix
  • Feature
  • Refactor
  • Breaking change
  • Docs / chore

Notes

Related: #5771 (a module that disappears before resume; different path, no overlap in code). Tracked in the umbrella #5653.

🤖 Generated with Claude Code

https://claude.ai/code/session_014MKndVj7XWvRQmvW9w2bDg


Generated by Claude Code


🐳 Agent Server images for this PR — GHCR package, pull/run commands, and all pushed tags (click to expand)

• GHCR package: https://github.com/OpenHands/agent-sdk/pkgs/container/agent-server

Variants & Base Images

Variant Architectures Base Image Docs / Tags
java amd64, arm64 eclipse-temurin:17-jdk Link
python-slim amd64, arm64 python-node-runtime Link
python-minimal amd64, arm64 python-node-runtime Link
python amd64, arm64 python-node-runtime Link
golang amd64, arm64 golang:1.21-bookworm Link

Pull (multi-arch manifest)

# Each variant is a multi-arch manifest supporting both amd64 and arm64
docker pull ghcr.io/openhands/agent-server:5f29be7-python

Run

docker run -it --rm \
  -p 8000:8000 \
  --name agent-server-5f29be7-python \
  ghcr.io/openhands/agent-server:5f29be7-python

All tags pushed for this build

ghcr.io/openhands/agent-server:5f29be7-golang-amd64
ghcr.io/openhands/agent-server:5f29be7b716a9ffe426575f95e39a4fc45294556-golang-amd64
ghcr.io/openhands/agent-server:claude-umbrella-bug-fixes-tgz9ai-tool-module-unimportable-golang-amd64
ghcr.io/openhands/agent-server:5f29be7-golang_tag_1.21-bookworm-amd64
ghcr.io/openhands/agent-server:5f29be7-golang-arm64
ghcr.io/openhands/agent-server:5f29be7b716a9ffe426575f95e39a4fc45294556-golang-arm64
ghcr.io/openhands/agent-server:claude-umbrella-bug-fixes-tgz9ai-tool-module-unimportable-golang-arm64
ghcr.io/openhands/agent-server:5f29be7-golang_tag_1.21-bookworm-arm64
ghcr.io/openhands/agent-server:5f29be7-java-amd64
ghcr.io/openhands/agent-server:5f29be7b716a9ffe426575f95e39a4fc45294556-java-amd64
ghcr.io/openhands/agent-server:claude-umbrella-bug-fixes-tgz9ai-tool-module-unimportable-java-amd64
ghcr.io/openhands/agent-server:5f29be7-eclipse-temurin_tag_17-jdk-amd64
ghcr.io/openhands/agent-server:5f29be7-java-arm64
ghcr.io/openhands/agent-server:5f29be7b716a9ffe426575f95e39a4fc45294556-java-arm64
ghcr.io/openhands/agent-server:claude-umbrella-bug-fixes-tgz9ai-tool-module-unimportable-java-arm64
ghcr.io/openhands/agent-server:5f29be7-eclipse-temurin_tag_17-jdk-arm64
ghcr.io/openhands/agent-server:5f29be7-python-amd64
ghcr.io/openhands/agent-server:5f29be7b716a9ffe426575f95e39a4fc45294556-python-amd64
ghcr.io/openhands/agent-server:claude-umbrella-bug-fixes-tgz9ai-tool-module-unimportable-python-amd64
ghcr.io/openhands/agent-server:5f29be7-python-node-runtime-amd64
ghcr.io/openhands/agent-server:5f29be7-python-arm64
ghcr.io/openhands/agent-server:5f29be7b716a9ffe426575f95e39a4fc45294556-python-arm64
ghcr.io/openhands/agent-server:claude-umbrella-bug-fixes-tgz9ai-tool-module-unimportable-python-arm64
ghcr.io/openhands/agent-server:5f29be7-python-node-runtime-arm64
ghcr.io/openhands/agent-server:5f29be7-python-minimal-amd64
ghcr.io/openhands/agent-server:5f29be7b716a9ffe426575f95e39a4fc45294556-python-minimal-amd64
ghcr.io/openhands/agent-server:claude-umbrella-bug-fixes-tgz9ai-tool-module-unimportable-python-minimal-amd64
ghcr.io/openhands/agent-server:5f29be7-python-node-runtime-minimal-amd64
ghcr.io/openhands/agent-server:5f29be7-python-minimal-arm64
ghcr.io/openhands/agent-server:5f29be7b716a9ffe426575f95e39a4fc45294556-python-minimal-arm64
ghcr.io/openhands/agent-server:claude-umbrella-bug-fixes-tgz9ai-tool-module-unimportable-python-minimal-arm64
ghcr.io/openhands/agent-server:5f29be7-python-node-runtime-minimal-arm64
ghcr.io/openhands/agent-server:5f29be7-python-slim-amd64
ghcr.io/openhands/agent-server:5f29be7b716a9ffe426575f95e39a4fc45294556-python-slim-amd64
ghcr.io/openhands/agent-server:claude-umbrella-bug-fixes-tgz9ai-tool-module-unimportable-python-slim-amd64
ghcr.io/openhands/agent-server:5f29be7-python-node-runtime-slim-amd64
ghcr.io/openhands/agent-server:5f29be7-python-slim-arm64
ghcr.io/openhands/agent-server:5f29be7b716a9ffe426575f95e39a4fc45294556-python-slim-arm64
ghcr.io/openhands/agent-server:claude-umbrella-bug-fixes-tgz9ai-tool-module-unimportable-python-slim-arm64
ghcr.io/openhands/agent-server:5f29be7-python-node-runtime-slim-arm64
ghcr.io/openhands/agent-server:5f29be7-golang
ghcr.io/openhands/agent-server:5f29be7b716a9ffe426575f95e39a4fc45294556-golang
ghcr.io/openhands/agent-server:claude-umbrella-bug-fixes-tgz9ai-tool-module-unimportable-golang
ghcr.io/openhands/agent-server:5f29be7-golang_tag_1.21-bookworm
ghcr.io/openhands/agent-server:5f29be7-java
ghcr.io/openhands/agent-server:5f29be7b716a9ffe426575f95e39a4fc45294556-java
ghcr.io/openhands/agent-server:claude-umbrella-bug-fixes-tgz9ai-tool-module-unimportable-java
ghcr.io/openhands/agent-server:5f29be7-eclipse-temurin_tag_17-jdk
ghcr.io/openhands/agent-server:5f29be7-python-minimal
ghcr.io/openhands/agent-server:5f29be7b716a9ffe426575f95e39a4fc45294556-python-minimal
ghcr.io/openhands/agent-server:claude-umbrella-bug-fixes-tgz9ai-tool-module-unimportable-python-minimal
ghcr.io/openhands/agent-server:5f29be7-python-node-runtime-minimal
ghcr.io/openhands/agent-server:5f29be7-python-slim
ghcr.io/openhands/agent-server:5f29be7b716a9ffe426575f95e39a4fc45294556-python-slim
ghcr.io/openhands/agent-server:claude-umbrella-bug-fixes-tgz9ai-tool-module-unimportable-python-slim
ghcr.io/openhands/agent-server:5f29be7-python-node-runtime-slim
ghcr.io/openhands/agent-server:5f29be7-python
ghcr.io/openhands/agent-server:5f29be7b716a9ffe426575f95e39a4fc45294556-python
ghcr.io/openhands/agent-server:claude-umbrella-bug-fixes-tgz9ai-tool-module-unimportable-python
ghcr.io/openhands/agent-server:5f29be7-python-node-runtime

About Multi-Architecture Support

  • Each variant tag (e.g., 5f29be7-python) is a multi-arch manifest supporting both amd64 and arm64
  • Docker automatically pulls the correct architecture for your platform
  • Individual architecture tags (e.g., 5f29be7-python-amd64) are also available if needed

…imported

A conversation whose agent names a tool from a tool_module_qualnames module
the server cannot import was created with only a warning, then every
message failed with an unhandled 500 (KeyError from resolve_tool). The
create now answers 422 naming the tool and the module. A module whose tool
the agent does not use stays a warning. The F33 map bullet drops its
known-bug marker.

Fixes #5940

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014MKndVj7XWvRQmvW9w2bDg
@github-actions

Copy link
Copy Markdown
Contributor

Coverage

Coverage Report •
FileStmtsMissCoverMissing
openhands-agent-server/openhands/agent_server
   conversation_router.py2951994%108–109, 113–114, 246, 389, 473, 520, 580, 761–764, 776–779, 819, 857
   conversation_service.py121711890%172–173, 182, 235–236, 240–241, 246, 416–417, 478, 583, 590–591, 692, 778, 829–830, 837, 869–870, 886, 917, 921, 933, 953, 974–975, 984, 1052, 1062, 1087, 1093–1094, 1098–1099, 1107, 1134, 1140, 1234, 1240, 1245, 1251, 1259–1260, 1269–1272, 1281, 1294, 1312, 1349, 1353–1357, 1380, 1429, 1442, 1450, 1508–1509, 1513, 1706–1708, 1710–1711, 1714–1715, 1757, 1838–1839, 1871, 1874, 1881–1883, 1886–1887, 1891–1893, 1896–1897, 1901–1903, 1906–1907, 1936, 1945, 2004–2006, 2067, 2070, 2097, 2107, 2112–2115, 2129, 2140, 2152–2153, 2185, 2282, 2341, 2399, 2414–2415, 2798, 2851, 2854
TOTAL497441981560% 

@github-actions

Copy link
Copy Markdown
Contributor

REST API breakage checks (OpenAPI) — ✅ PASSED

Result: ✅ PASSED

Action log

@enyst
enyst marked this pull request as ready for review October 14, 2026 03:01
@enyst
enyst requested a review from all-hands-bot October 14, 2026 03:04

@all-hands-bot all-hands-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This review was posted by an AI agent (OpenHands).

Review — PR #5958

Scope check. The change belongs in this repository: it touches the agent-server create path and the verify-agent-server feature map, both owned here, and it implements the API-contract choice issue #5940 asks for (reject the create with a 4xx). Issue #5940 is open with ready-for-dev, bug, priority:low. No product/architecture escalation is needed.

Verification performed on head 5f29be7b (all CI check runs green, including agent-server-tests, cross-tests, security).

  • Focused tests: tests/agent_server/test_conversation_service.py + tests/agent_server/test_conversation_router.py -> 233 passed.
  • Live repro against a real agent server started from this head: a create whose agent names qa_ghost_tool with tool_module_qualnames={"qa_ghost_tool": "qa_ghost"} (module not on the server sys.path) returns 422 with a detail naming the tool and the module (qa_ghost: No module named 'qa_ghost'), and no conversation is persisted.
  • Control case: the same unimportable module whose tool the agent does not use still returns 201 and starts the conversation.
  • uv run pre-commit run --files <changed files> passes.

The fix is correct for its scope. The check runs after the agent is finalized but before any conversation state is written, check_usable=False correctly avoids rejecting a registered-but-unusable tool, and the router maps the new UnavailableAgentTool to 422 alongside the existing client-tool registration error on the same route. Both acceptance criteria for the unimportable-module case hold, and the feature-map update matches the observed behavior.

Non-blocking follow-up (outside this PR's scope; not a blocker). The rejection is keyed on tools present in request.tool_module_qualnames, so the same 500-on-every-message remains reachable through two sibling triggers, both reproduced on this head:

  1. an agent that names a tool registered nowhere and with no tool_module_qualnames entry -> create 201, then 500 KeyError: "ToolDefinition 'qa_nonexistent_tool' is not registered"; and
  2. a tool_module_qualnames entry whose module imports but does not register the named tool -> create 201, then the same 500.

The issue's acceptance criteria are scoped to the unimportable-module case, so this PR fully satisfies them. Widening the check to any agent tool that is_tool_available(..., check_usable=False) cannot resolve would close the remaining path and is worth a separate issue.

✅ APPROVED

@enyst
enyst merged commit 615759c into main Oct 17, 2026
56 of 57 checks passed
@enyst
enyst deleted the claude/umbrella-bug-fixes-tgz9ai-tool-module-unimportable branch October 17, 2026 03:16
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.

[Bug]: Conversation whose agent names a tool from a module the server cannot import is created, then every message is a 500

3 participants