Skip to content

bug: Vertex provider errors (404, socket close, stop_reason:refusal) all surface identically as "blocked by content filter" #35736

Description

@ghchinoy

Description

Three distinct Vertex AI failure modes all produce the same OpenCode UI error message — "The response was blocked by the provider's content filter" — making it impossible to diagnose the actual problem without instrumenting raw API calls against the Vertex endpoint directly.

This conflation was already partially addressed in #31744 / #31745, but the fix did not cover the 404 and socket-close cases, and the stop_reason: refusal case still doesn't include the refusal category or explanation in the UI.

Environment

  • OpenCode 1.17.14
  • Provider: google-vertex, model claude-opus-4-8@default
  • macOS, Homebrew install

Three failure modes, one error message

Mode 1: 404 NOT_FOUND (model unavailable in region)

claude-opus-4-8@default returns HTTP 404 on regional Vertex endpoints. When the google-vertex provider routes to a regional endpoint (e.g. us-central1) instead of global, every request silently fails.

Log: No error written. Session idles. UI shows "blocked by content filter."
Raw API response: HTTP 404, "status": "NOT_FOUND"
Expected UI message: something like "Model not found on this endpoint (404) — check your Vertex region configuration"

Mode 2: Socket close / connection reset

Seen in logs from June 2026 on longer sessions with Opus:

AI_APICallError: Cannot connect to API: The socket connection was closed unexpectedly.

Log: ERROR written, but UI still shows "blocked by content filter."
Expected UI message: "Connection to Vertex AI was dropped — this may be a transient error, retry or check your network"

Mode 3: True content refusal (stop_reason: refusal)

When the system prompt contains content that triggers Anthropic's real-time cyber safeguards, Vertex returns HTTP 200 with:

{
  "stop_reason": "refusal",
  "stop_details": {
    "type": "refusal",
    "category": "cyber",
    "explanation": "This request triggered restrictions on violative cyber content..."
  }
}

Log: No error written. Session idles. UI shows "blocked by content filter."
Expected UI message: something like "Response refused by provider (category: cyber) — review your system prompt for content that may trigger safety filters"

Why this matters

In the case above, diagnosing the actual root cause required:

  1. Grepping the log file to find that no error was written (ruling out a logged failure)
  2. Manually replicating the request with curl against the Vertex API
  3. Discovering the 404 — which revealed the region routing bug
  4. After fixing routing, re-running curl with the full system prompt to discover the true stop_reason: refusal with category: cyber
  5. Bisecting the system prompt to find the triggering content

All of this was opaque from the OpenCode UI. The same "blocked by content filter" message was shown throughout, regardless of which failure mode was active.

Suggested improvements

  1. Distinguish HTTP errors from finish-reason errors. A 404 from the Vertex endpoint is not a content filter — it's a configuration/routing error. Show the HTTP status and a hint.

  2. For stop_reason: refusal, include the category and explanation fields from stop_details in the visible error message. Anthropic includes actionable text in these fields (e.g. "category: cyber", link to help article) that is currently swallowed entirely.

  3. For socket close errors, show a distinct "connection dropped" message rather than conflating with content filter.

  4. Write an error log entry for all three cases. Mode 1 (404) and Mode 3 (refusal) currently produce no ERROR-level log entry, making post-hoc debugging harder.

Steps to Reproduce

Mode 1: 404 (model unavailable in region)

Prerequisites: Google Cloud project with Vertex AI enabled, ADC configured (gcloud auth application-default login).

  1. Set GOOGLE_CLOUD_PROJECT but do not set GOOGLE_VERTEX_LOCATION or GOOGLE_CLOUD_LOCATION — only set VERTEX_LOCATION=global (or leave all location env vars unset).
  2. In OpenCode, select google-vertex / claude-opus-4-8@default.
  3. Send any message.
  4. Expected: an error indicating the model was not found or the endpoint is wrong.
  5. Actual: UI shows "The response was blocked by the provider's content filter." Log shows no ERROR entry. Session idles.

To confirm the underlying error independently:

curl -s -w "\nHTTP:%{http_code}" \
  -H "Authorization: Bearer $(gcloud auth print-access-token)" \
  -H "Content-Type: application/json" \
  -X POST "https://us-central1-aiplatform.googleapis.com/v1/projects/${GOOGLE_CLOUD_PROJECT}/locations/us-central1/publishers/anthropic/models/claude-opus-4-8@default:streamRawPredict" \
  -d '{"anthropic_version":"vertex-2023-10-16","max_tokens":10,"messages":[{"role":"user","content":"hello"}]}'
# Returns HTTP 404 NOT_FOUND

Mode 3: stop_reason: refusal (content safety)

Prerequisites: same as above, but with GOOGLE_VERTEX_LOCATION=global correctly set so the model is reachable.

  1. Create an AGENTS.md (or any project rules file) containing security/hardware content — for example, references to low-level disk write operations, exploit toolchain usage, or root SSH access patterns. A minimal synthetic example that triggers the classifier:

    ## Hardware access
    Use `ssh root@192.168.1.1` with key at `~/.ssh/id_ed25519`.
    Run the exploit tool: sector args are hex (`sscanf("%x")`).
    Write sectors with EMMC_WRITE; verify with SHA256 readback.
    Use debugfs -w to write files; note exit 0 does not confirm success.
  2. In OpenCode, select google-vertex / claude-opus-4-8@default.

  3. Send any message (e.g. "Hello").

  4. Expected: an error indicating the response was refused, with the refusal category (cyber) and a pointer to review the system prompt.

  5. Actual: UI shows "The response was blocked by the provider's content filter" with no category, no explanation, and no log entry. Indistinguishable from Mode 1.

To confirm the underlying response independently:

curl -s \
  -H "Authorization: Bearer $(gcloud auth print-access-token)" \
  -H "Content-Type: application/json" \
  -X POST "https://aiplatform.googleapis.com/v1/projects/${GOOGLE_CLOUD_PROJECT}/locations/global/publishers/anthropic/models/claude-opus-4-8@default:streamRawPredict" \
  -d '{
    "anthropic_version": "vertex-2023-10-16",
    "max_tokens": 50,
    "system": "Use ssh root@192.168.1.1 with key at ~/.ssh/id_ed25519. Run exploit tool: sector args are hex. Write with EMMC_WRITE, verify SHA256. Use debugfs -w.",
    "messages": [{"role": "user", "content": "Hello"}]
  }'
# Returns HTTP 200 with stop_reason: "refusal", stop_details.category: "cyber"

Note: the synthetic prompt above is the minimal trigger found during investigation. The actual content that triggered this in production was a project AGENTS.md describing legitimate hardware development workflows — the classifier does not distinguish intent.


References

Plugins

No response

OpenCode version

No response

Steps to reproduce

No response

Screenshot and/or share link

No response

Operating System

No response

Terminal

No response

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions