Skip to content

Amazon Bedrock: a single tool name >64 chars rejects the entire Converse toolConfig, silently dropping MCP tools / subagents / providers #53828

Description

@EricOHansen5

Summary

AWS Bedrock's Converse API enforces toolSpec.name ∈ ^[a-zA-Z0-9_-]{1,64}$ (max 64 chars, unique per request). opencode serializes every exposed tool (built-ins, MCP server tools, and subagent dispatch tools) into toolConfig.tools[]. If any one name violates the cap, Bedrock rejects the whole request with:

ValidationException: ... Member must have length less than or equal to 64 ...

The practical failure is silent and non-local: at session start the offending tool's whole surface (and in our case several subagents and even the provider) disappears from the dispatchable/available set, with no clear error surfaced to the user. The user just finds an agent "cannot run as a subagent" / missing.

Repro

  1. Configure an MCP server whose auto-generated tool names are long. Example (Power Platform CLI MCP, pac ≥ 2.12.1):
    pac_licensing_get-user-per-flow-capacity-source-flow-context-summary-for-user-id = 80 chars.
  2. Use an amazon-bedrock Converse model (e.g. Claude on Bedrock).
  3. Start a session. Expect: session fails to start, OR MCP tools/subagents/provider silently vanish depending on where the overflow lands.

Current user-side mitigations (all brittle avoidance, not fixes)

  • Pin the MCP server to an older version that lacks the long-named verbs (blocks upgrades; next long name reintroduces it).
  • Per-agent permission deny of whole MCP surfaces (pac_*, azure-devops_*) so the long names never serialize (manual, easy to miss — we had 4 subagents silently dropped because their deny lists weren't exhaustive).

Proposed fix (core)

Normalize tool names in the Bedrock request builder (the amazon-bedrock/mantle serialization layer), immediately before constructing toolConfig:

  • For any name > 64 chars or containing a char outside [a-zA-Z0-9_-] or colliding, map it to a deterministic ≤64 alias (e.g. head(name, 64-9) + "__" + sha1(name)[:8]), keeping a reverse map for the toolUse/toolResult round-trip.
  • Apply uniformly to built-ins, MCP, and subagent tools.

This makes the class of bug impossible rather than avoided, and is invisible to the model (names are opaque identifiers to it).

Alternatives considered

  • Per-tool MCP include-list (expose only used tools) — helps but still manual curation.
  • Fail-loud validation at startup (list every >64 name and refuse rather than silently drop) — strictly better than today even if the rename isn't adopted; at minimum do this.

Workaround available today (plugin API)

The v2 Promise plugin API exposes ctx.tool.transform(editor => ...) with editor.list()/.add()/.remove() and ctx.tool.list() documented as "keyed by effective name". A local plugin can rename >64 names to ≤64 aliases at registration time; since opencode drives tool-use by the effective name, no reverse map is needed plugin-side. (Attached: bedrock-tool-name-cap.js.) This confirms the fix is correct in principle and could be upstreamed into the Bedrock builder so plugins aren't required.

Severity

High — silent capability loss (subagents/providers vanish) with a misleading downstream error, on a supported provider, triggered merely by installing/upgrading an MCP server.

Activity

  1. opencode-agent commented on Oct 7, 2026

    @opencode-agent
    Contributor

    Thanks for the detailed write-up! Before we try to reproduce this, could you describe briefly, in your own words, what you did, what you expected and what you actually saw? We also need a few specifics:

    1. OpenCode version: the output of opencode --version, plus your OS and how you installed OpenCode.
    2. Your MCP config: in particular, whether the pac server (or any other MCP server) has codemode: false. In v2, MCP tools run through Code Mode by default. Bedrock then only sees a single execute tool, so a long MCP tool name shouldn't reach toolConfig. In the current code, it can only exceed 64 characters when Code Mode is turned off for that server.
    3. The exact error: the full ValidationException text and the related log lines. Without them we can't tell what you mean by subagents and the provider "vanishing" (for example, the exact message you get when an agent "cannot run as a subagent").
    4. The plugin you mentioned (bedrock-tool-name-cap.js): it isn't attached to the issue, so please paste it if you can.

    You can reply here, or edit the issue. If you edit it, please also leave a comment saying you've updated it, so we know when you're done.

    These issues are related. The list is ordered by how likely each one is to cover your problem:

  2. EricOHansen5 commented on Oct 8, 2026

    @EricOHansen5
    Author

    Thanks — and your Code Mode point is well taken. After digging into our own logs I now think my original root-cause (the 64-char toolConfig overflow) is probably wrong for our setup, and the real symptom is narrower than I framed it. Here are the specifics you asked for, and a corrected read.

    1. Version / OS / install

    • opencode v2.0.25
    • Windows 11 Pro (26300), PowerShell 7
    • Installed via npm global (nvm4w): C:\nvm4w\nodejs\opencode.ps1
    • Provider: amazon-bedrock (US-Gov), Claude models via Converse.

    2. MCP config / Code Mode

    No server sets codemode at all, so all MCP servers use the v2 default (Code Mode ON). Confirmed in-session: the agent only sees the execute tool + a search catalog, exactly as you describe. Our servers and their connection tool counts (from the log, i.e. what the server exposes, NOT what reaches Bedrock): pac=70, azure-devops=49, playwright=25, codegraph=1. So you're right — under Code Mode those long pac_*/azure-devops_* names are collapsed behind execute and shouldn't reach toolConfig.

    3. The exact error (and what I now believe is actually happening)

    There is no ValidationException and no "Member must have length ≤ 64" anywhere in the log. I went back through the full ~/.local/share/opencode/log/opencode.log and found only INFO/mcp connected lines plus two unrelated config-normalization WARNs ($.small_model, $.compaction.prune — "unsupported"). So the dramatic "provider vanishes" framing in my original report was not something I actually observed in logs — I inferred it. I retract that part.

    What I did reproducibly observe: calling the Task/subagent tool with certain agents returns, synchronously:

    Agent <name> cannot run as a subagent
    

    This is a client-side guard, not a Bedrock error, and nothing is logged server-side for it.

    Crucially, two of the agents that hit this are mode: primary (dataverse-frontend-agent, cicd-migration-engineer) — for those the message is correct (a primary legitimately can't be a subagent); that was my own confusion, not a bug.

    The one genuine anomaly: catms-security-auditor is mode: subagent (verified in its frontmatter) yet still returns "cannot run as a subagent", and this persisted across a full process restart (confirmed the serving opencode serve --service PID post-dated the restart). That's the behavior I can't explain and would like help with — it looks unrelated to tool-name length. If there's a known reason a mode: subagent agent is excluded from a primary's dispatchable set (e.g. an allow-list, a model/variant resolution failure, a description/front-matter parse issue), that's the real lead. Happy to run any --print-logs --log-level debug repro you want.

    4. The plugin (bedrock-tool-name-cap.js)

    Full source below. Given #2 above, I now suspect this plugin is a no-op in our Code-Mode setup (it only renames names >64, and under Code Mode none reach it), so it is not what's gating the auditor. I'm no longer treating it as a fix; posting it only because you asked.

    import { Plugin } from "@opencode/plugin"
    import { createHash } from "node:crypto"
    
    const MAX = 64
    const HASH_LEN = 8
    const SEP = "__"
    
    function aliasFor(name) {
      const hash = createHash("sha1").update(name).digest("hex").slice(0, HASH_LEN)
      const keep = MAX - SEP.length - HASH_LEN
      const head = name.slice(0, keep).replace(/[^a-zA-Z0-9_-]/g, "_")
      return `${head}${SEP}${hash}`
    }
    
    export default Plugin.define({
      id: "bedrock-tool-name-cap",
      setup(ctx) {
        let registration
        try {
          registration = ctx.tool.transform((editor) => {
            try {
              const seen = new Set()
              for (const tool of editor.list()) {
                const id = tool.id
                if (typeof id !== "string") continue
                const tooLong = id.length > MAX
                const badChar = /[^a-zA-Z0-9_-]/.test(id)
                const dup = seen.has(id)
                if (!tooLong && !badChar && !dup) { seen.add(id); continue }
                let alias = aliasFor(id)
                let salt = 0
                while (seen.has(alias)) alias = aliasFor(id + "#" + ++salt)
                seen.add(alias)
                try { editor.add({ ...tool, name: alias }); editor.remove(id) }
                catch (e) { console.warn(`bedrock-tool-name-cap: could not rename "${id}": ${e?.message ?? e}`) }
              }
            } catch (e) { console.warn(`bedrock-tool-name-cap: transform body failed: ${e?.message ?? e}`) }
          })
        } catch (e) { console.warn(`bedrock-tool-name-cap: failed to register transform (no-op): ${e?.message ?? e}`) }
        return async () => { try { const r = await registration; if (r?.dispose) await r.dispose() } catch {} }
      },
    })

    Net

    Please treat this as two separable things: (a) the 64-char toolConfig risk — you've shown Code Mode makes it a non-issue by default, so this may only matter for servers with codemode: false (if so, a fail-loud startup check per your "alternatives" would still be a nice guardrail, and the related #34485/#48750 largely cover it); and (b) the actual thing that bit me — a mode: subagent agent being undispatchable with "cannot run as a subagent", surviving a restart. I'm glad to re-file (b) as a clean, separate issue with a minimal repro + debug logs if that's more useful than reusing this one. Apologies for the misdirected framing on (a).

  3. opencode-agent commented on Oct 8, 2026

    @opencode-agent
    Contributor

    Thanks for going back through your logs and for the clear correction. That settles part (a): with Code Mode on (the default), Bedrock only sees execute, so long MCP tool names can't overflow toolConfig. Since you didn't see a ValidationException, there's nothing to fix here for your setup. The codemode: false case is related to #34485, #38808 and #48750.

    For part (b), yes, please open it as a separate issue. It's a different problem from this one. Some pointers that may help you narrow it down first:

    • In v2 the message Agent <name> cannot run as a subagent is only returned when the agent's resolved mode is exactly primary (packages/core/src/tool/plugin/subagent.ts). If the agent weren't found at all, you'd get Unknown agent: <name> instead. So something is turning catms-security-auditor into primary even though its file says mode: subagent.
    • Some likely causes, based on packages/core/src/config/plugin/agent.ts:
      1. The file is under a mode/ or modes/ folder. Files there are always forced to mode: primary, whatever their frontmatter says. Only agent/ or agents/ keep the frontmatter mode.
      2. Another definition with the same name. For example an agent entry for catms-security-auditor in opencode.json (global or project), or a second catms-security-auditor.md in another agent folder (e.g. both ~/.config/opencode/agents/ and <project>/.opencode/agents/). Each definition overrides the earlier one, so a later one with mode: primary wins.
      3. Agents with no mode at all default to primary. That only matters here if the frontmatter isn't being read as you expect, e.g. the file contains keys from the old v1 format, so it gets converted.

    When you open the new issue, please include:

    • the full path(s) of every file and config entry that defines catms-security-auditor
    • its frontmatter
    • the primary agent you're calling it from
    • your version (2.0.25), and debug logs if you have them

    This issue will be closed automatically, since the original report no longer applies.

  4. opencode-agent commented on Oct 10, 2026

    @opencode-agent
    Contributor

    Closing this as not planned. As you found, with Code Mode on (the default) Bedrock only sees execute, so long MCP tool names can't overflow toolConfig, and your logs had no ValidationException. The codemode: false case is related to #34485, #38808 and #48750.

    The problem you still have, catms-security-auditor returning cannot run as a subagent even though its file says mode: subagent, is a separate issue. Please open a new issue for it with:

    • the path of every file or config entry that defines that agent (look especially for files under mode//modes/ folders, and for other definitions with the same name)
    • its frontmatter
    • the primary agent you call it from
    • your version and debug logs

    Thanks for taking the time to dig into this and correct the report.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions