spacehub

surshar / claude-code

Claude Code is an agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster by executing routine tasks, explaining complex code, and handling git workflows - all through natural language commands.

Built by Anthropic · github.com/anthropics

claude-code / ANT_ONLY_METADATA_USER_ID_SPEC.md
221 lines · 8.6 KBPreviewCodeRaw
1# ANT_ONLY_METADATA_USER_ID Implementation Specification
2
3## Overview
4
5This document specifies the implementation of the `ANT_ONLY_METADATA_USER_ID` environment variable feature for Claude Code. This feature allows internal Anthropic jobs (like Claude Oracle, Oncall, Ambient, etc.) to pass custom metadata through Claude Code sessions to the Anthropic API for tracking, rate limiting, and incident triage purposes.
6
7## Background
8
9From the Slack discussion:
10- Internal API jobs launched via Claude Code currently lack user metadata showing `job_id` or `username` in `api_usage`
11- ~85% of requests from heavy CC users (like Closing The Loop org) have no user_metadata or job identifier
12- This metadata is critical for:
13 - Managing Internal API rate limits
14 - Triaging incidents
15 - Setting up alerting that pings the DRI researchers
16 - Understanding which jobs are driving token usage
17
18## Requirements
19
201. **Environment Variable**: Claude Code must read an environment variable called `ANT_ONLY_METADATA_USER_ID`
212. **API Integration**: The value must be passed to all Anthropic Messages API calls in the `metadata.user_id` field
223. **Tree Shaking**: Only Anthropic internal builds should be able to use this feature
234. **Format**: The expected format is a JSON object: `{"user": "<anthropic_username>", "job": "<job_name>"}`
24 - Example: `{"user": "tedm", "job": "tedm-1208-mu-g-18x"}`
25 - Or: `{"user": "vinke", "job": "claude-oracle:fluorine:vinke"}`
26
27## Implementation
28
29### 1. Environment Variable Reading
30
31In the appropriate configuration/env module (likely `src/config/env.ts` or similar):
32
33```typescript
34// Environment variable for Anthropic internal builds only
35// This is used to pass job metadata to the API for usage tracking
36export function getAntOnlyMetadataUserId(): string | undefined {
37 // Only available in Anthropic internal builds
38 if (!isAnthropicBuild()) {
39 return undefined;
40 }
41 return process.env.ANT_ONLY_METADATA_USER_ID;
42}
43```
44
45### 2. API Request Metadata
46
47When constructing API requests to the Anthropic Messages API, add the metadata:
48
49```typescript
50// In the API client module (e.g., src/api/anthropic.ts)
51import { getAntOnlyMetadataUserId } from '../config/env';
52
53function buildApiRequestMetadata(): Record<string, string> | undefined {
54 const metadataUserId = getAntOnlyMetadataUserId();
55
56 if (!metadataUserId) {
57 return undefined;
58 }
59
60 return {
61 user_id: metadataUserId
62 };
63}
64
65// When creating a message:
66const response = await client.messages.create({
67 model: selectedModel,
68 max_tokens: maxTokens,
69 messages: conversationMessages,
70 // ... other parameters
71 metadata: buildApiRequestMetadata()
72});
73```
74
75### 3. Tree Shaking for Internal Builds
76
77The feature should be conditionally compiled out for public builds using build-time flags:
78
79```typescript
80// Using a build-time constant
81declare const __ANT_INTERNAL_BUILD__: boolean;
82
83export function isAnthropicBuild(): boolean {
84 // This will be replaced at build time
85 // For internal builds: true
86 // For public builds: false (and the code will be tree-shaken)
87 if (typeof __ANT_INTERNAL_BUILD__ !== 'undefined') {
88 return __ANT_INTERNAL_BUILD__;
89 }
90 return false;
91}
92```
93
94In the build configuration (esbuild/webpack/rollup):
95
96```javascript
97// For public builds:
98define: {
99 '__ANT_INTERNAL_BUILD__': 'false'
100}
101
102// For internal builds:
103define: {
104 '__ANT_INTERNAL_BUILD__': 'true'
105}
106```
107
108### 4. Validation (Optional but Recommended)
109
110```typescript
111function validateMetadataUserId(value: string): boolean {
112 try {
113 const parsed = JSON.parse(value);
114 return typeof parsed.user === 'string' && typeof parsed.job === 'string';
115 } catch {
116 // Also allow simple string format for backward compatibility
117 return typeof value === 'string' && value.length > 0;
118 }
119}
120
121export function getValidatedAntOnlyMetadataUserId(): string | undefined {
122 const value = getAntOnlyMetadataUserId();
123 if (value && !validateMetadataUserId(value)) {
124 console.warn('ANT_ONLY_METADATA_USER_ID has invalid format. Expected JSON: {"user": "...", "job": "..."}');
125 return undefined;
126 }
127 return value;
128}
129```
130
131## Usage by Internal Services
132
133Services like Claude Oracle should set this environment variable before launching Claude Code sessions:
134
135```python
136# In Claude Oracle / coo job code
137import os
138import json
139
140# Set the metadata before spawning Claude Code
141os.environ['ANT_ONLY_METADATA_USER_ID'] = json.dumps({
142 "user": username, # Anthropic username
143 "job": job_name # e.g., "claude-oracle:fluorine:vinke"
144})
145
146# Then launch Claude Code session...
147```
148
149Or via command line:
150
151```bash
152ANT_ONLY_METADATA_USER_ID='{"user":"tedm","job":"tedm-1208-mu-g-18x"}' claude --headless ...
153```
154
155## Data Flow
156
157```
158┌─────────────────────────────────────────────────────────────────┐
159│ COO Job / Oracle / Oncall / etc. │
160│ Sets: ANT_ONLY_METADATA_USER_ID='{"user":"..","job":".."}' │
161└─────────────────────────────────────────────────────────────────┘
162 │
163 ▼
164┌─────────────────────────────────────────────────────────────────┐
165│ Claude Code CLI │
166│ Reads: process.env.ANT_ONLY_METADATA_USER_ID │
167│ Adds to API calls: metadata: { user_id: "..." } │
168└─────────────────────────────────────────────────────────────────┘
169 │
170 ▼
171┌─────────────────────────────────────────────────────────────────┐
172│ Anthropic API │
173│ Receives: metadata.user_id in request │
174│ Stores in: api_usage.metadata_user_id (BigQuery) │
175└─────────────────────────────────────────────────────────────────┘
176 │
177 ▼
178┌─────────────────────────────────────────────────────────────────┐
179│ BigQuery: anthropic.api_production.api_usage │
180│ Fields populated: │
181│ - metadata_user_id │
182│ - device_id (if included) │
183│ - claude_code_session_id (if included) │
184└─────────────────────────────────────────────────────────────────┘
185```
186
187## Testing
188
1891. **Unit Tests**:
190 - Test that `getAntOnlyMetadataUserId()` returns undefined when env var is not set
191 - Test that it returns the value when set (in internal builds)
192 - Test validation of JSON format
193
1942. **Integration Tests**:
195 - Verify metadata is included in API requests
196 - Verify tree-shaking removes the code in public builds
197
1983. **E2E Validation** (after deployment):
199 - Run a Claude Code session with the env var set
200 - Query BigQuery `anthropic.api_production.api_usage` to verify `metadata_user_id` is populated
201
202## Security Considerations
203
2041. **Internal Only**: The tree-shaking ensures this feature is not available in public builds
2052. **No Secrets**: The metadata should not contain sensitive information
2063. **Validation**: Input validation prevents injection attacks
207
208## Related Files to Modify (Internal Repo)
209
210Based on patterns in the bundled CLI, likely files to modify:
211
2121. `src/config/env.ts` or similar - Add env var reading
2132. `src/api/anthropic.ts` or similar - Add metadata to API requests
2143. `build.config.ts` or similar - Add build-time flag for internal builds
2154. Tests for the above
216
217## References
218
219- Slack thread: Internal API metadata discussion
220- BigQuery tables: `anthropic.api_production.api_usage`, `api_events`
221- Anthropic API docs: [Messages API metadata parameter](https://docs.anthropic.com/en/api/messages)