Policy Limits Service
- Entity ID:
ent-20260410-3aa0434da5eb - Type:
service - Scope:
shared - Status:
active
Description
The Policy Limits Service fetches and enforces organization-level policy restrictions from the Anthropic API. It lives at src/services/policyLimits/ and follows a fail-open design: if the API is unreachable or returns an error, the CLI continues without restrictions. The service supports both API key (Console) and OAuth (Claude.ai) authentication, but for OAuth users only Team and Enterprise subscribers are eligible, since only those organizations have admin-configurable policy restrictions.
The service implements a multi-layer caching strategy. On startup, loadPolicyLimits() is called during CLI initialization, which fetches restrictions from the /api/claude_code/policy_limits endpoint with retry logic (up to 5 retries with exponential backoff) and a 10-second timeout. Responses are cached to a local file (policy-limits.json in the Claude config home directory) and to a module-level sessionCache variable. The fetch uses HTTP conditional requests: a SHA-256 checksum of the restrictions is computed via computeChecksum() and sent as If-None-Match, and a 304 Not Modified response means the cache is still valid. After the initial load, a background polling interval runs every hour (POLLING_INTERVAL_MS = 3,600,000ms) to pick up mid-session policy changes. The polling timer is .unref()'d so it does not prevent Node.js from exiting.
Policy checks are synchronous via isPolicyAllowed(policy), which reads from the session cache or falls back to the file cache. The restrictions schema is a simple record mapping policy names to { allowed: boolean } objects -- only blocked policies are included; absent policies are implicitly allowed. One notable exception to the fail-open rule: policies in ESSENTIAL_TRAFFIC_DENY_ON_MISS (currently just allow_product_feedback) fail closed when essential-traffic-only mode is active and the cache is unavailable, preventing HIPAA organizations from accidentally re-enabling feedback collection on a cache miss. A loading promise with a 30-second timeout (LOADING_PROMISE_TIMEOUT_MS) allows other systems to await waitForPolicyLimitsToLoad() without risking deadlocks.
Key claims
- Fail-open by default:
isPolicyAllowed()returnstruewhen restrictions arenull(unavailable) or the policy key is absent from the restrictions map (src/services/policyLimits/index.tsline 510-526). - Exception for HIPAA:
ESSENTIAL_TRAFFIC_DENY_ON_MISS = new Set(['allow_product_feedback'])causesisPolicyAllowed()to returnfalsewhen essential-traffic-only mode is active and cache is unavailable (line 502-519). - Background polling interval is 1 hour:
const POLLING_INTERVAL_MS = 60 * 60 * 1000(line 58), with the interval timer.unref()'d (line 645). - Fetch uses 5 retries with exponential backoff via
fetchWithRetry()(line 267-295) and a 10-second timeout per request:const FETCH_TIMEOUT_MS = 10000(line 57). - The restrictions schema is
z.record(z.string(), z.object({ allowed: z.boolean() }))defined insrc/services/policyLimits/types.ts(line 9-12), validated with Zod on every fetch and cache read.
Relations
- implemented-by:
src/services/policyLimits/index.ts-- main service logic (fetch, cache, poll, check) - schema:
src/services/policyLimits/types.ts--PolicyLimitsResponseSchema,PolicyLimitsFetchResult - initialized-by:
src/entrypoints/init.ts-- callsloadPolicyLimits()during CLI startup - consumed-by: various components that call
isPolicyAllowed()to gate features - depends-on:
src/utils/auth.ts--getAnthropicApiKeyWithSource(),getClaudeAIOAuthTokens(),checkAndRefreshOAuthTokenIfNeeded()
Sources
src/services/policyLimits/index.tssrc/services/policyLimits/types.ts