Common quirks and troubleshooting
Skill patterns to catch before you generate, and the errors a self-hosted skill agent most often hits.
Before you startA generated agent, or a skill you are about to convert.View as Markdown
Check the skill before you convert it
| Symptom in the skill | What to do |
|---|---|
Scripts hardcode /sessions/<session-id>/... paths | Create the exact path in the Dockerfile and flag the brittleness. One mkdir usually covers every script. |
SKILL.md names a CLI tool, or scripts call subprocess, os.system, or os.popen | Install the binary in the Dockerfile, at the exact path the skill looks for it. |
A bootstrap or auth flow references tokens.json or ~/.config/<name>/ | Do not bake credentials or wire OAuth into the agent. Do the first OAuth login on a laptop with the same skill in Claude Code, upload the tokens to a file share, mount it, and point the skill's env var at the mount. |
Imports such as feedparser, youtube-transcript-api, arxiv, or praw | Add them to the dependencies and confirm the container's egress can reach the public internet. Check the skill tolerates one source failing. |
| Multi-MB outputs | File share for files under 100 MB; blob storage (blobfuse or an upload sidecar) for larger or many files. Note size estimates in the README. |
| The skill calls other skills | Bake every skill it needs into /app/.claude/skills/, say so in the prompt, and use the multi-purpose pattern. |
The skill keeps state (state.json, history.db) | Mount a file share on the state directory. Per-conversation state needs a context id from the caller; treat that as out of scope for a first version. |
| The skill needs a newer Python | The image uses python:3.12-slim. Bump it if needed; never go below 3.10, which the SDK requires. |
Scripts call input() or getpass(), or open an editor | Not viable unattended. Refactor the skill to take everything through arguments or env first. |
| The skill imports a package that is not on PyPI | Stop. That is a blocker. |
Errors and what they mean
| You see | Cause and fix |
|---|---|
/health returns not_configured | Read the missing-vars list in the response body's detail. |
| AMC registration says Unreachable but the container is running | /health returned 503 because the health probe is not ready, or the URL redirects. See Deploy and register in AMC. |
| The agent runs but never invokes the skill | Check /app/.claude/skills/<skill-name>/SKILL.md exists in the container, SKILL_NAME matches the frontmatter name:, allowed_tools includes "Skill", and setting_sources=["project"] is set. |
--dangerously-skip-permissions cannot be used with root/sudo privileges | Something switched to bypassPermissions while running as root. Restore permission_mode="dontAsk" and the non-root user. |
Permission denied when the skill writes | The directory is missing from {{NONROOT_EXTRA_DIRS}}, so user claude does not own it. |
JSON message exceeded maximum buffer size | A message passed the SDK's 1 MiB framing limit. Set CLAUDE_AGENT_MAX_BUFFER_BYTES. |
A Run used N turns (max_turns=50) warning that says approaching cap | Raise CLAUDE_AGENT_MAX_TURNS. |
Error: assistant message error: authentication_failed (or billing_error, rate_limit) | Credentials, billing, or quota. Run tests/test_foundry_direct.py to isolate it. |
Error: agent completed but produced no text response | Look for an earlier AssistantMessage error or a ResultMessage flagged is_error=True line in the logs. |
| 401 from Foundry | Run tests/test_foundry_direct.py, then test_anthropic_sdk_foundry.py, to find which layer fails. |
Phase 1 fails with Claude Code returned an error result | Run tests/debug_sdk.py for the full message dump. Check the SDK is at least 0.2.138. |
| Tokens always report 0 | Code reads flat input_tokens attributes that do not exist. Read ResultMessage.model_usage. |
| AMC prices the run at the default rate | step_completed has no model. Pass it, with cached_input_tokens. |
| A failed run shows zero cost in AMC | set_step_tokens and set_cost were not called before re-raising. |
| Prompt tokens are much higher than the job needs | tools= is unset, so the full Claude Code toolset is defined in every prompt. Set it to the base set. |
Related
Last updated 2026-10-21