Skip to content

Gateway runs well-formed-but-unknown agent slug under agents.defaults instead of 4xx (no roster validation in resolveAgentIdForRequest; x-openclaw-agent-id header never roster-validated) #92504

Description

@ryanhelms

Summary

The gateway's OpenAI-compatible chat-completions endpoint (POST /v1/chat/completions) silently runs a well-formed but unknown agent target under agents.defaults (the root persona) instead of returning a 4xx. There is no roster validation of the resolved agent id. This produces "phantom agent" replies that are indistinguishable from a real agent to the calling integration.

Verified against the gateway 2026.5.26 dist.

Two distinct gaps

(a) Unknown model: openclaw/<slug> falls through to agents.defaults

When a client sends model: openclaw/<slug> (or openclaw:<slug>, agent:<slug>) where <slug> is not a configured agent, resolveAgentIdForRequest (in the gateway HTTP http-utils) resolves it to the default agent and the turn executes under agents.defaults with a synthesized workspace. No error is surfaced.

This is especially dangerous for dashed-GUID slugs: a malformed integration that passes a conversation/user GUID as the agent slug gets a fully successful 200 response generated by the root persona. (This is the shape of the 2026-05-17 phantom-agent incident we hit downstream: an Office DM created against a non-roster slug produced a confident reply attributed to a "team agent" that does not exist.)

Expected: a well-formed request whose model names an unknown agent slug should return a structured 4xx (e.g. 404 unknown_agent / 422), not a 200 from the default agent. Silent fallback to agents.defaults should at minimum be opt-in (config flag), not the default behavior.

(b) x-openclaw-agent-id header is sanitized but never roster-validated

The x-openclaw-agent-id override header takes precedence over the model field. It is sanitized (format/charset) but the resolved agent id is never checked against the configured agent roster. So the same phantom-agent fallthrough happens via the header path, and it bypasses any validation a caller might expect from the model field.

Expected: the header-resolved agent id should be roster-validated identically to the model-resolved id; an unknown id should 4xx.

Impact

Integrations that build the model/x-openclaw-agent-id value from user- or data-derived input (conversation routing, DM creation, etc.) cannot distinguish "agent ran" from "unknown slug silently ran as root". The only safe workaround today is to validate the slug against a roster snapshot before calling the gateway — which is what we've now done on our side (ByteDesk BDP-1986), but the gateway should fail closed on unknown agent targets.

Suggested fix

  • In resolveAgentIdForRequest, after resolving the target agent id (from model or x-openclaw-agent-id), verify it exists in the configured agent set. If not, return a structured 4xx instead of falling back to agents.defaults.
  • Gate any intentional default-fallback behind an explicit config flag (default off).

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1High-priority user-facing bug, regression, or broken workflow.clawsweeper:fix-shape-clearClawSweeper found a clear likely implementation shape for this issue.clawsweeper:queueable-fixClawSweeper marked this issue as an existing queue_fix_pr work candidate.clawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.impact:otherThis issue has meaningful maintainer-visible impact outside the owned taxonomy.impact:session-stateSession, memory, transcript, context, or agent state can drift or corrupt.issue-rating: 🦞 diamond lobsterVery strong issue quality with high-confidence source-level or clear reproduction.

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions