Self-hosted API gateway for Claude Code on Amazon Bedrock. Team API keys, per-user budgets, OIDC SSO, rate limiting, and an admin portal.
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
claude-opus-4-8 → retired Opus 4.0 incident): the model cache matched requested IDs with starts_with ordered by prefix length, so a request for claude-opus-4-8 could shadow-match a row keyed claude-opus-4 and silently route to a retired inference profile, causing Bedrock 400s. Hardened across the read and write paths so the bug class cannot recur.ModelCache now stores mappings in a HashMap and looks them up by exact equality (O(1)), not prefix matching. The hardcoded fallback was likewise converted from greedy starts_with catch-alls to exact-match arms — a future claude-sonnet-4-8 now passes through to discovery instead of misrouting to retired Sonnet 4.0.claude-opus-4-8), never a bare major (claude-opus-4) — so dated aliases resolve without reopening the greedy shadow.discover_model persists the exact requested model ID (Slice 3) as anthropic_prefix, not the lossy date-stripped form (the root cause of the original incident). Profile selection now runs three ordered passes — exact stem, versioned stem, fuzzy contains — to disambiguate variants like claude-opus-4-8-thinking.model_seed.json is embedded at compile time and inserted at startup with ON CONFLICT DO NOTHING, so a request for a known model succeeds on a fresh deploy before any discovery tick. It is a floor, not a ceiling — discovery still handles anything unseeded. Explicitly not a runtime/call-home fetch.permissions.deny: ["WebSearch"] to Claude Code clientsGET/PUT /admin/websearch-mode for reading and setting the mode, with provider config for global modeGET /admin/settings so the portal and clients can read ithas_api_key boolean)SearchProvider::from_global_config(): constructs a search provider from admin-configured JSON with validation (provider_type required, api_key required for Tavily/Serper, api_url required for Custom, max_results clamped to 1-20)extract_web_search_tool_with_mode(): mode-aware tool extraction that strips web_search tools when mode is disabledtranslate() now accepts a websearch_mode parameter; handler reads mode from DB settings on each requestuser_claim configuration: controls which JWT claim is used as the user identifier. Configurable via portal UI, admin API, or OIDC_USER_CLAIM env var. Supports email, preferred_username, upn, oid, name, sub, or auto (default fallback chain).preferred_username, upn, oid, and name claims from OIDC JWTs (previously only email and sub were extracted).email and profile scopes in addition to openid, for broader claim availability.email > preferred_username > upn > name > sub by default (was sub only). This fixes Entra ID (Azure AD) compatibility where sub is an opaque pairwise hash.detail no longer includes source, event_type, or timestamp fields — these are redundant with the EventBridge envelope (source, detail-type, time). Webhook and SNS payloads are unchanged. Breaking for consumers parsing these fields from EventBridge detail.ccag login) redirect URL no longer breaks when the IDP audience is a UUID client_id (common with Entra ID). The audience is only used as a redirect hostname when it looks like a domain name.OIDC_ISSUER is a one-time bootstrap seed (not a persistent override). Changing it after first startup has no effect unless the IDP is deleted from the portal first.ADMIN_USERS description with OIDC subject identifier details and startup seeding behavior.alarmWebhookUrl reference. Separated from app-level notifications.