- Request ID:
- ae01bf03-a421-49b8-badd-5bea35674249
- Conversation ID:
- 10865b49-7dc4-42a4-acac-8c7aa42ab76e
- Parent Request:
- 182fbdcc-1309-4a81-afd5-9ac0d3bb18ab
- Branch:
- main
- Domain:
- localhost:3000
- Model:
- claude-opus-4-20250514
- Timestamp:
- 7/24/2025, 9:03:50 AM
- Tokens:
- Input: 3 Output: 82 Total: 85
- Cost:
- $0.0062
- Duration:
- 4s
- Status:
- 200
Tool Use: Bash (ID: toolu_01FY4BqfYjp8wTn7QQZ9sFpU)
{
"command": "git push origin feat/add-readme-screenshots",
"description": "Push changes to remote branch"
}
Tool Result (ID: toolu_01MB2JKFVYsjq4zLLjfe3nCJ)
Todos have been modified successfully. Ensure that you continue to use the todo list to track your progress. Please proceed with the current tasks if applicable
<system-reminder>
Your todo list has changed. DO NOT mention this explicitly to ...
Show more (5 lines)
Tool Result (ID: toolu_01MB2JKFVYsjq4zLLjfe3nCJ)
Todos have been modified successfully. Ensure that you continue to use the todo list to track your progress. Please proceed with the current tasks if applicable
<system-reminder>
Your todo list has changed. DO NOT mention this explicitly to the user. Here are the latest contents of your todo list:
[{"content":"Stage and commit the TypeScript fix","status":"completed","priority":"high","id":"1"},{"content":"Push changes to remote branch","status":"in_progress","priority":"high","id":"2"},{"content":"Create pull request","status":"pending","priority":"high","id":"3"}]. Continue on with the tasks at hand if applicable.
</system-reminder>
Show less
Tool Use: TodoWrite (ID: toolu_01MB2JKFVYsjq4zLLjfe3nCJ)
{
"todos": [
{
"id": "1",
"status": "completed",
"content": "Stage and commit the TypeScript fix",
"priority": "high"
},
{
"id": "2",
"status": "in_progress",
"content": "Pus...
Show more (12 lines)
Tool Use: TodoWrite (ID: toolu_01MB2JKFVYsjq4zLLjfe3nCJ)
{
"todos": [
{
"id": "1",
"status": "completed",
"content": "Stage and commit the TypeScript fix",
"priority": "high"
},
{
"id": "2",
"status": "in_progress",
"content": "Push changes to remote branch",
"priority": "high"
},
{
"id": "3",
"status": "pending",
"content": "Create pull request",
"priority": "high"
}
]
}
Show less
Tool Result (ID: toolu_014p72sUF62uDv85k4a865H6)
[feat/add-readme-screenshots 47f84fd] fix: resolve TypeScript type instantiation depth error in analyses route
1 file changed, 2 insertions(+), 6 deletions(-)
[STARTED] Backing up original state...
[COMPLETED] Backed up original state in git...
Show more (24 lines)
Tool Result (ID: toolu_014p72sUF62uDv85k4a865H6)
[feat/add-readme-screenshots 47f84fd] fix: resolve TypeScript type instantiation depth error in analyses route
1 file changed, 2 insertions(+), 6 deletions(-)
[STARTED] Backing up original state...
[COMPLETED] Backed up original state in git stash (1547122)
[STARTED] Running tasks for staged files...
[STARTED] .lintstagedrc.json β 1 file
[STARTED] packages/**/*.{js,ts,tsx,jsx} β 0 files
[STARTED] services/**/*.{js,ts,tsx,jsx} β 1 file
[STARTED] scripts/**/*.{js,ts,tsx,jsx} β 0 files
[STARTED] *.{json,md,mdx,yml,yaml} β 0 files
[STARTED] *.css β 0 files
[SKIPPED] packages/**/*.{js,ts,tsx,jsx} β no files
[SKIPPED] scripts/**/*.{js,ts,tsx,jsx} β no files
[SKIPPED] *.{json,md,mdx,yml,yaml} β no files
[SKIPPED] *.css β no files
[STARTED] eslint --fix
[COMPLETED] eslint --fix
[STARTED] prettier --write
[COMPLETED] prettier --write
[COMPLETED] services/**/*.{js,ts,tsx,jsx} β 1 file
[COMPLETED] .lintstagedrc.json β 1 file
[COMPLETED] Running tasks for staged files...
[STARTED] Applying modifications from tasks...
[COMPLETED] Applying modifications from tasks...
[STARTED] Cleaning up temporary files...
[COMPLETED] Cleaning up temporary files...
Show less
Tool Use: Bash (ID: toolu_014p72sUF62uDv85k4a865H6)
{
"command": "git commit -m \"$(cat <<'EOF'\nfix: resolve TypeScript type instantiation depth error in analyses route\n\nReplace imported uuidSchema with inline z.string().uuid() to avoid circular dependency causing TS2589 error.\n\n...
Show more (4 lines)
Tool Use: Bash (ID: toolu_014p72sUF62uDv85k4a865H6)
{
"command": "git commit -m \"$(cat <<'EOF'\nfix: resolve TypeScript type instantiation depth error in analyses route\n\nReplace imported uuidSchema with inline z.string().uuid() to avoid circular dependency causing TS2589 error.\n\nπ€ Generated with [Claude Code](https://claude.ai/code)\n\nCo-Authored-By: Claude <noreply@anthropic.com>\nEOF\n)\"",
"description": "Commit the TypeScript fix"
}
Show less
Tool Result (ID: toolu_01YKC7MUxCtURFzgPfYLYFsi)
docs: add Key Concepts section to explain terminology
Tool Use: Bash (ID: toolu_01YKC7MUxCtURFzgPfYLYFsi)
{
"command": "git log -1 --pretty=format:\"%s\"",
"description": "Check recent commit message style"
}
Tool Result (ID: toolu_01VLcGc3HmfiGk9KEyxN6y9d)
I need to unstage the README.md changes since we only want to commit the TypeScript fix:
Tool Use: Bash (ID: toolu_01VLcGc3HmfiGk9KEyxN6y9d)
{
"command": "git restore --staged README.md",
"description": "Unstage README.md changes"
}
Tool Result (ID: toolu_01NKJam6edULbSw8yu7fiRhd)
diff --git a/README.md b/README.md
index 2a93720..885e250 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,7 @@
# Claude Nexus Proxy
-A high-performance proxy for Claude API with comprehensive monitoring, conversation tracking, and dashbo...
Show more (175 lines)
Tool Result (ID: toolu_01NKJam6edULbSw8yu7fiRhd)
diff --git a/README.md b/README.md
index 2a93720..885e250 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,7 @@
# Claude Nexus Proxy
-A high-performance proxy for Claude API with comprehensive monitoring, conversation tracking, and dashboard visualization.
+A high-performance proxy for Claude Code with comprehensive monitoring, conversation tracking, and dashboard visualization.
+(_Supports Claude Max plan_)
## π― Objectives
@@ -39,21 +40,15 @@ Understanding these terms will help you navigate Claude Nexus Proxy effectively:
### Core Concepts
- **π£οΈ Conversation**: A complete interaction session between a user and Claude, consisting of multiple message exchanges. Each conversation has a unique ID and can span multiple requests.
-
- **π³ Branch**: When you edit an earlier message in a conversation and continue from there, it creates a new branch - similar to Git branches. This allows exploring alternative conversation paths without losing the original.
-
- **π¦ Compact**: When a conversation exceeds Claude's context window, it's automatically summarized and continued as a "compact" conversation, preserving the essential context while staying within token limits.
-
- **π€ Sub-task**: When Claude spawns another AI agent using the Task tool, it creates a sub-task. These are tracked separately but linked to their parent conversation for complete visibility.
### Technical Terms
- **π€ Token**: The basic unit of text that Claude processes. Monitoring token usage helps track costs and stay within API limits.
-
- **π Request**: A single API call to Claude, which may contain multiple messages. Conversations are built from multiple requests.
-
- **π§ Tool Use**: Claude's ability to use external tools (like file reading, web search, or spawning sub-tasks). Each tool invocation is tracked and displayed.
-
- **π MCP (Model Context Protocol)**: A protocol for managing and sharing prompt templates across teams, with GitHub integration for version control.
### Dashboard Elements
@@ -106,7 +101,7 @@ For developers who need complete visibility, access the raw JSON view of any req
- [Bun](https://bun.sh) runtime (v1.0+)
- PostgreSQL database
-- Claude API key
+- Claude Plan (_or Claude API Key_)
### Installation
@@ -122,50 +117,18 @@ bun install
cp .env.example .env
# Edit .env with your settings
-# Initialize database
-bun run db:migrate:token-usage
-
# Start development servers
bun run dev
The proxy runs on http://localhost:3000
and dashboard on http://localhost:3001
.
-### Using Claude CLI with the Proxy +### Using Claude Code with the Proxy
Run Claude CLI connected to your local proxy:
-# Start the proxy and Claude CLI
-docker compose --profile dev --profile claude up -d
-
-# Access Claude CLI
-docker compose exec claude-cli claude
-
-# Or run a single command
-docker compose exec claude-cli claude "What is 2+2?"
-```
-
-The Claude CLI will use Bearer token authentication to connect through the proxy.
-
-### Viewing Proxy Logs
-
-After running Claude queries, you can view the proxy logs to debug issues:
-
-```bash
-# View recent logs
-docker compose logs proxy
-
-# Follow logs in real-time
-docker compose logs -f proxy
-
-# Use the helper script for filtered views
-./scripts/view-claude-logs.sh --help
-
-# Examples:
-./scripts/view-claude-logs.sh -f # Follow logs
-./scripts/view-claude-logs.sh -e -n 100 # Show last 100 errors
-./scripts/view-claude-logs.sh -r # Show API requests
+API_TIMEOUT_MS=300000 DISABLE_NON_ESSENTIAL_MODEL_CALLS=1 ANTHROPIC_BASE_URL=http://localhost:3000 claude
Configuration
@@ -187,7 +150,6 @@ DASHBOARD_API_KEY=your-secure-key
Optional Features
STORAGE_ENABLED=true DEBUG=false -SLACK_WEBHOOK_URL=https://hooks.slack.com/...
See the [Documentation](docs/README.md) for complete configuration options.
@@ -204,13 +166,21 @@ bun run auth:generate-key
cat > credentials/example.com.credentials.json << EOF
{
"type": "api_key",
- "accountId": "acc_unique_identifier",
+ "accountId": "acc_name_to_display",
"api_key": "sk-ant-...",
"client_api_key": "cnp_live_..."
}
EOF
+(Use credentials/localhost\:3000.credentials.json
for using it locally)
+
+Authenticate your credential with Claude MAX Plan:
+
+bash +./scripts/auth/oauth-login.ts credentials/example.com.credentials.json +
+
Usage
API Proxy
@@ -274,14 +244,13 @@ bun run db:rebuild-conversations # Rebuild conversation data bun run ai:check-jobs # Check analysis job statuses bun run ai:check-content # Inspect analysis content bun run ai:reset-stuck # Reset jobs with high retry counts -bun run ai:fail-exceeded # Manually fail jobs exceeding retries
See [DEVELOPMENT.md](docs/DEVELOPMENT.md) for development guidelines.
## Deployment
-### Environments
+### Environments (MoonsongLabs Internal)
Claude Nexus Proxy supports deployment to multiple environments:
@@ -314,12 +283,10 @@ See [AWS Infrastructure Guide](docs/03-Operations/deployment/aws-infrastructure.
```bash
# Build and run with locally built images
./docker-local.sh up -d --build
-
-# Or manually:
-cd docker
-docker compose -f docker-compose.local.yml --env-file ../.env up -d --build
+(dashboard key: key
)
+
Building Images Separately
diff --git a/services/proxy/src/routes/analyses.ts b/services/proxy/src/routes/analyses.ts
index 6da9c5f..52d0808 100644
--- a/services/proxy/src/routes/analyses.ts
+++ b/services/proxy/src/routes/analyses.ts
@@ -6,15 +6,11 @@ import {
rateLimitAnalysisCreation,
rateLimitAnalysisRetrieval,
} from '../middleware/analysis-rate-limit.js'
-import {
- ConversationAnalysisStatus,
- uuidSchema,
- conversationBranchParamsSchema,
-} from '@claude-nexus/shared'
+import { ConversationAnalysisStatus, conversationBranchParamsSchema } from '@claude-nexus/shared'
// Request/Response schemas
const createAnalysisSchema = z.object({
- conversationId: uuidSchema,
+ conversationId: z.string().uuid(),
branchId: z.string(),
customPrompt: z.string().optional(),
})
Show less
Tool Use: Bash (ID: toolu_01NKJam6edULbSw8yu7fiRhd)
{
"command": "git diff --cached",
"description": "Check staged changes"
}
Tool Result (ID: toolu_01WQSpC54mjMAcTKwAJ8abkm)
On branch feat/add-readme-screenshots
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: README.md
modified: services/proxy/src/routes/analyses.ts
Tool Use: Bash (ID: toolu_01WQSpC54mjMAcTKwAJ8abkm)
{
"command": "git status",
"description": "Check git status"
}
Tool Result (ID: toolu_01WQkvWYnck7sVtMoZdfSQHd)
Todos have been modified successfully. Ensure that you continue to use the todo list to track your progress. Please proceed with the current tasks if applicable
<system-reminder>
Your todo list has changed. DO NOT mention this explicitly to ...
Show more (5 lines)
Tool Result (ID: toolu_01WQkvWYnck7sVtMoZdfSQHd)
Todos have been modified successfully. Ensure that you continue to use the todo list to track your progress. Please proceed with the current tasks if applicable
<system-reminder>
Your todo list has changed. DO NOT mention this explicitly to the user. Here are the latest contents of your todo list:
[{"content":"Stage and commit the TypeScript fix","status":"in_progress","priority":"high","id":"1"},{"content":"Push changes to remote branch","status":"pending","priority":"high","id":"2"},{"content":"Create pull request","status":"pending","priority":"high","id":"3"}]. Continue on with the tasks at hand if applicable.
</system-reminder>
Show less
Tool Use: TodoWrite (ID: toolu_01WQkvWYnck7sVtMoZdfSQHd)
{
"todos": [
{
"id": "1",
"status": "in_progress",
"content": "Stage and commit the TypeScript fix",
"priority": "high"
},
{
"id": "2",
"status": "pending",
"content": "Push ...
Show more (12 lines)
Tool Use: TodoWrite (ID: toolu_01WQkvWYnck7sVtMoZdfSQHd)
{
"todos": [
{
"id": "1",
"status": "in_progress",
"content": "Stage and commit the TypeScript fix",
"priority": "high"
},
{
"id": "2",
"status": "pending",
"content": "Push changes to remote branch",
"priority": "high"
},
{
"id": "3",
"status": "pending",
"content": "Create pull request",
"priority": "high"
}
]
}
Show less
Tool Result (ID: toolu_01AyE2RyciVeoxC8qx34LQdt)
Todos have been modified successfully. Ensure that you continue to use the todo list to track your progress. Please proceed with the current tasks if applicable
<system-reminder>
Your todo list has changed. DO NOT mention this explicitly to ...
Show more (5 lines)
Tool Result (ID: toolu_01AyE2RyciVeoxC8qx34LQdt)
Todos have been modified successfully. Ensure that you continue to use the todo list to track your progress. Please proceed with the current tasks if applicable
<system-reminder>
Your todo list has changed. DO NOT mention this explicitly to the user. Here are the latest contents of your todo list:
[{"content":"Stage and commit the TypeScript fix","status":"pending","priority":"high","id":"1"},{"content":"Push changes to remote branch","status":"pending","priority":"high","id":"2"},{"content":"Create pull request","status":"pending","priority":"high","id":"3"}]. Continue on with the tasks at hand if applicable.
</system-reminder>
Show less
I'll commit the changes, push to the current branch, and create a PR.
Tool Use: TodoWrite (ID: toolu_01AyE2RyciVeoxC8qx34LQdt)
{
"todos": [
{
"id": "1",
"status": "pending",
"content": "Stage and commit the TypeScript fix",
"priority": "high"
},
...
Show more (15 lines)
I'll commit the changes, push to the current branch, and create a PR.
Tool Use: TodoWrite (ID: toolu_01AyE2RyciVeoxC8qx34LQdt)
{
"todos": [
{
"id": "1",
"status": "pending",
"content": "Stage and commit the TypeScript fix",
"priority": "high"
},
{
"id": "2",
"status": "pending",
"content": "Push changes to remote branch",
"priority": "high"
},
{
"id": "3",
"status": "pending",
"content": "Create pull request",
"priority": "high"
}
]
}
Show less
Commit, Push and create a PR
<system-reminder>This is a reminder that your todo list is currently empty. DO NOT mention this to the user explicitly because they are already aware. If you are working on tasks that would benefit from a todo list please use the TodoWrite tool to create one. If no...
Show more (8 lines)Commit, Push and create a PR
<system-reminder>This is a reminder that your todo list is currently empty. DO NOT mention this to the user explicitly because they are already aware. If you are working on tasks that would benefit from a todo list please use the TodoWrite tool to create one. If not, please feel free to ignore. Again do not mention this message to the user.</system-reminder>
<system-reminder> The TodoWrite tool hasn't been used recently. If you're working on tasks that would benefit from tracking progress, consider using the TodoWrite tool to track progress. Only use it if it's relevant to the current work. This is just a gentle reminder - ignore if not applicable. </system-reminder> Show less
No response requested.
Commit, Push and create a PR
Great! The TypeScript error has been fixed. The issue was that importing uuidSchema
from the shared package was causing a type instantiation depth issue. By using z.string().uuid()
directly instead of the imported uuidSchema
, we've resolved the circular dependency or complex type inference iss...
Great! The TypeScript error has been fixed. The issue was that importing uuidSchema
from the shared package was causing a type instantiation depth issue. By using z.string().uuid()
directly instead of the imported uuidSchema
, we've resolved the circular dependency or complex type inference issue.
Tool Result (ID: toolu_01MiL67pM7ewe1MAHB6h5eaN)
$ tsc --build --force
Tool Use: Bash (ID: toolu_01MiL67pM7ewe1MAHB6h5eaN)
{
"command": "bun run typecheck",
"description": "Run TypeScript type checking again"
}
Tool Result (ID: toolu_0187yJdNQ9YPq18wd2x2WBNJ)
The file /home/crystalin/projects/claude-nexus-proxy/services/proxy/src/routes/analyses.ts has been updated. Here's the result of running `cat -n` on a snippet of the edited file:
11β uuidSchema,
12β conversationBranchParamsSchema,
...
Show more (13 lines)
Tool Result (ID: toolu_0187yJdNQ9YPq18wd2x2WBNJ)
The file /home/crystalin/projects/claude-nexus-proxy/services/proxy/src/routes/analyses.ts has been updated. Here's the result of running `cat -n` on a snippet of the edited file:
11β uuidSchema,
12β conversationBranchParamsSchema,
13β} from '@claude-nexus/shared'
14β
15β// Request/Response schemas
16βconst createAnalysisSchema = z.object({
17β conversationId: z.string().uuid(),
18β branchId: z.string(),
19β customPrompt: z.string().optional(),
20β})
21β
22βconst getAnalysisParamsSchema = conversationBranchParamsSchema
23β
24β// Audit logging helper
Show less
The issue persists. Let me try a different approach by simplifying the schema definition:
Tool Use: Edit (ID: toolu_0187yJdNQ9YPq18wd2x2WBNJ)
{
"file_path": "/home/crystalin/projects/claude-nexus-proxy/services/proxy/src/routes/analyses.ts",
"new_string": "// Request/Response ...
Show more (4 lines)
The issue persists. Let me try a different approach by simplifying the schema definition:
Tool Use: Edit (ID: toolu_0187yJdNQ9YPq18wd2x2WBNJ)
{
"file_path": "/home/crystalin/projects/claude-nexus-proxy/services/proxy/src/routes/analyses.ts",
"new_string": "// Request/Response schemas\nconst createAnalysisSchema = z.object({\n conversationId: z.string().uuid(),\n branchId: z.string(),\n customPrompt: z.string().optional(),\n})",
"old_string": "// Request/Response schemas\ntype CreateAnalysisInput = {\n conversationId: string\n branchId: string\n customPrompt?: string\n}\n\nconst createAnalysisSchema: z.ZodSchema<CreateAnalysisInput> = z.object({\n conversationId: uuidSchema,\n branchId: z.string(),\n customPrompt: z.string().optional(),\n})"
}
Show less
Tool Result (ID: toolu_01LxiFMyCpLsw8Q5596FFFCL)
$ tsc --build --force
services/proxy/src/routes/analyses.ts(22,64): error TS2589: Type instantiation is excessively deep and possibly infinite.
Now let's run the TypeScript check again to see if this resolves the issue:
Tool Use: Bash (ID: toolu_01LxiFMyCpLsw8Q5596FFFCL)
{
"command": "bun run typecheck",
"description": "Run TypeScript type checking to verify the fix"
}
Tool Result (ID: toolu_01YVFx2HiQZWRWTaq9CPxkq2)
The file /home/crystalin/projects/claude-nexus-proxy/services/proxy/src/routes/analyses.ts has been updated. Here's the result of running `cat -n` on a snippet of the edited file:
11β uuidSchema,
12β conversationBranchParamsSchema,
...
Show more (19 lines)
Tool Result (ID: toolu_01YVFx2HiQZWRWTaq9CPxkq2)
The file /home/crystalin/projects/claude-nexus-proxy/services/proxy/src/routes/analyses.ts has been updated. Here's the result of running `cat -n` on a snippet of the edited file:
11β uuidSchema,
12β conversationBranchParamsSchema,
13β} from '@claude-nexus/shared'
14β
15β// Request/Response schemas
16βtype CreateAnalysisInput = {
17β conversationId: string
18β branchId: string
19β customPrompt?: string
20β}
21β
22βconst createAnalysisSchema: z.ZodSchema<CreateAnalysisInput> = z.object({
23β conversationId: uuidSchema,
24β branchId: z.string(),
25β customPrompt: z.string().optional(),
26β})
27β
28βconst getAnalysisParamsSchema = conversationBranchParamsSchema
29β
30β// Audit logging helper
Show less
The issue appears to be with how TypeScript is resolving the type of z.object()
. This can happen when there's a circular dependency or when Zod's type inference becomes too complex. Let me fix this by explicitly typing the schema:
Tool Use: Edit (ID: toolu_01YVFx2HiQZWRWTaq9CPxkq2)
Show more (7 lines)
The issue appears to be with how TypeScript is resolving the type of z.object()
. This can happen when there's a circular dependency or when Zod's type inference becomes too complex. Let me fix this by explicitly typing the schema:
Tool Use: Edit (ID: toolu_01YVFx2HiQZWRWTaq9CPxkq2)
{
"file_path": "/home/crystalin/projects/claude-nexus-proxy/services/proxy/src/routes/analyses.ts",
"new_string": "// Request/Response schemas\ntype CreateAnalysisInput = {\n conversationId: string\n branchId: string\n customPrompt?: string\n}\n\nconst createAnalysisSchema: z.ZodSchema<CreateAnalysisInput> = z.object({\n conversationId: uuidSchema,\n branchId: z.string(),\n customPrompt: z.string().optional(),\n})",
"old_string": "// Request/Response schemas\nconst createAnalysisSchema = z.object({\n conversationId: uuidSchema,\n branchId: z.string(),\n customPrompt: z.string().optional(),\n})"
}
Show less
Tool Result (ID: toolu_01GLgWVBFHvx45rRjTNxFnpe)
1β// Re-export all shared modules
2βexport * from './types/index.js'
3βexport * from './config/index.js'
4βexport * from './logger/index.js'
5βexport * from './utils/errors.js'
6βexport * from './utils/conversati...
Show more (122 lines)
Tool Result (ID: toolu_01GLgWVBFHvx45rRjTNxFnpe)
1β// Re-export all shared modules
2βexport * from './types/index.js'
3βexport * from './config/index.js'
4βexport * from './logger/index.js'
5βexport * from './utils/errors.js'
6βexport * from './utils/conversation-hash.js'
7βexport * from './utils/conversation-linker.js'
8βexport * from './utils/system-reminder.js'
9βexport * from './utils/validation.js'
10β
11β// Re-export specific functions to ensure they're available
12βexport {
13β getErrorMessage,
14β getErrorStack,
15β getErrorCode,
16β hasStatusCode,
17β isError,
18β getStatusCode,
19β} from './utils/errors.js'
20β
21βexport { createLogger } from './logger/index.js'
22β
23βexport {
24β hashMessagesOnly,
25β hashSystemPrompt,
26β extractMessageHashes,
27β generateConversationId,
28β} from './utils/conversation-hash.js'
29β
30βexport { config } from './config/index.js'
31β
32βexport {
33β ConversationLinker,
34β type QueryExecutor,
35β type CompactSearchExecutor,
36β type RequestByIdExecutor,
37β type SubtaskQueryExecutor,
38β type SubtaskSequenceQueryExecutor,
39β type LinkingRequest,
40β type LinkingResult,
41β type ParentQueryCriteria,
42β type TaskInvocation,
43β} from './utils/conversation-linker.js'
44β
45βexport { stripSystemReminder, containsSystemReminder } from './utils/system-reminder.js'
46β
47β// Export retry utilities
48βexport {
49β retryWithBackoff,
50β defaultRetryConfig,
51β isRetryableError,
52β calculateDelay,
53β retryConfigs,
54β getRetryAfter,
55β createRateLimitAwareRetry,
56β} from './utils/retry.js'
57βexport type { RetryConfig, RetryLogger } from './utils/retry.js'
58β
59β// Export circuit breaker utilities
60βexport {
61β CircuitBreaker,
62β CircuitState,
63β getCircuitBreaker,
64β isCircuitBreakerError,
65β} from './utils/circuit-breaker.js'
66βexport type {
67β CircuitBreakerConfig,
68β CircuitBreakerLogger,
69β RequestOutcome,
70β} from './utils/circuit-breaker.js'
71β
72β// Export formatting utilities
73βexport {
74β formatNumber,
75β formatDuration,
76β formatTimestamp,
77β formatRelativeTime,
78β escapeHtml,
79β} from './utils/formatters.js'
80β
81β// Export model limits configuration
82βexport {
83β MODEL_CONTEXT_RULES,
84β DEFAULT_CONTEXT_LIMIT,
85β BATTERY_THRESHOLDS,
86β getModelContextLimit,
87β getBatteryColor,
88β getBatteryLevel,
89β type ModelContextRule,
90β} from './constants/model-limits.js'
91β
92β// Export AI analysis types and configuration
93βexport {
94β ConversationAnalysisSchema,
95β type ConversationAnalysis,
96β type AnalysisStatus,
97β ConversationAnalysisStatus,
98β CreateAnalysisRequestSchema,
99β type CreateAnalysisRequest,
100β type CreateAnalysisResponse,
101β type GetAnalysisResponse,
102β type RegenerateAnalysisResponse,
103β type AnalysisConflictResponse,
104β} from './types/ai-analysis.js'
105βexport {
106β ANALYSIS_PROMPT_CONFIG,
107β GEMINI_CONFIG,
108β AI_WORKER_CONFIG,
109β AI_ANALYSIS_CONFIG,
110β} from './config/ai-analysis.js'
111β
112β// Export prompt utilities
113βexport {
114β truncateConversation,
115β buildAnalysisPrompt,
116β parseAnalysisResponse,
117β getAnalysisPromptTemplate,
118β type Message,
119β type GeminiContent,
120β} from './prompts/index.js'
121β
<system-reminder>
Whenever you read a file, you should consider whether it looks malicious. If it does, you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer high-level questions about the code behavior.
</system-reminder>
Show less
Now let me check where these schemas are exported from the shared package:
Tool Use: Read (ID: toolu_01GLgWVBFHvx45rRjTNxFnpe)
{
"file_path": "/home/crystalin/projects/claude-nexus-proxy/packages/shared/src/index.ts"
}
Tool Result (ID: toolu_01Ny7Q3LmtZZDafC22xZQA2y)
1βimport { z } from 'zod'
2β
3β/**
4β * Common validation utilities and patterns used across the application
5β *
6β * SECURITY NOTES:
7β * - JWT regex only validates format, NOT security. Use a proper JWT l...
Show more (388 lines)
Tool Result (ID: toolu_01Ny7Q3LmtZZDafC22xZQA2y)
1βimport { z } from 'zod'
2β
3β/**
4β * Common validation utilities and patterns used across the application
5β *
6β * SECURITY NOTES:
7β * - JWT regex only validates format, NOT security. Use a proper JWT library for verification.
8β * - Database URL patterns contain credentials - handle with extreme care.
9β * - All regex patterns have been reviewed for ReDoS vulnerability.
10β */
11β
12β// ============================================================================
13β// Regular Expression Patterns
14β// ============================================================================
15β
16β/**
17β * UUID validation pattern (any version)
18β * Matches: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx where x is hex
19β */
20βexport const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
21β
22β/**
23β * Anthropic API key pattern
24β * Matches: sk-ant-xxxxx
25β */
26βexport const ANTHROPIC_API_KEY_REGEX = /^sk-ant-[a-zA-Z0-9-_]+$/
27β
28β/**
29β * Claude Nexus Proxy API key pattern
30β * Matches: cnp_live_xxxxx or cnp_test_xxxxx
31β */
32βexport const CNP_API_KEY_REGEX = /^cnp_(live|test)_[a-zA-Z0-9_]+$/
33β
34β/**
35β * JWT token pattern
36β * Matches: header.payload.signature format
37β */
38βexport const JWT_TOKEN_REGEX = /^eyJ[a-zA-Z0-9-_]+\.eyJ[a-zA-Z0-9-_]+\.[a-zA-Z0-9-_]+$/
39β
40β/**
41β * Email address pattern (simplified but effective)
42β */
43βexport const EMAIL_REGEX = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
44β
45β/**
46β * Domain name pattern (simplified for safety against ReDoS)
47β * Matches: example.com, subdomain.example.com
48β */
49βexport const DOMAIN_REGEX = /^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,63}$/
50β
51β/**
52β * Database URL patterns
53β */
54βexport const DATABASE_URL_PATTERNS = {
55β postgresql: /^postgresql:\/\/[^@]+@[^/]+\/\w+/,
56β mysql: /^mysql:\/\/[^@]+@[^/]+\/\w+/,
57β mongodb: /^mongodb(\+srv)?:\/\/[^@]+@[^/]+\/\w+/,
58β}
59β
60β/**
61β * Bearer token Authorization header pattern
62β * Matches: Bearer <jwt>
63β */
64βexport const BEARER_TOKEN_REGEX =
65β /^Bearer\s+(eyJ[a-zA-Z0-9-_]+\.eyJ[a-zA-Z0-9-_]+\.[a-zA-Z0-9-_]+)$/
66β
67β/**
68β * URL-friendly slug pattern
69β * Matches: my-resource-name-123
70β */
71βexport const SLUG_REGEX = /^[a-z0-9]+(?:-[a-z0-9]+)*$/
72β
73β/**
74β * Common Content-Type patterns
75β */
76βexport const CONTENT_TYPE_PATTERNS = {
77β json: /^application\/json(?:;.*)?$/,
78β text: /^text\/plain(?:;.*)?$/,
79β formUrlEncoded: /^application\/x-www-form-urlencoded(?:;.*)?$/,
80β multipart: /^multipart\/form-data(?:;.*)?$/,
81β}
82β
83β/**
84β * Semantic Versioning (SemVer) 2.0.0 pattern
85β * See: https://semver.org/
86β */
87βexport const SEMVER_REGEX =
88β /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/
89β
90β// ============================================================================
91β// Validation Functions
92β// ============================================================================
93β
94β/**
95β * Validates if a string is a valid UUID
96β */
97βexport function isValidUUID(value: string): boolean {
98β return UUID_REGEX.test(value)
99β}
100β
101β/**
102β * Validates if a string is a valid Anthropic API key
103β */
104βexport function isValidAnthropicApiKey(value: string): boolean {
105β return ANTHROPIC_API_KEY_REGEX.test(value)
106β}
107β
108β/**
109β * Validates if a string is a valid CNP API key
110β */
111βexport function isValidCNPApiKey(value: string): boolean {
112β return CNP_API_KEY_REGEX.test(value)
113β}
114β
115β/**
116β * Validates if a string is a valid JWT token format
117β */
118βexport function isValidJWT(value: string): boolean {
119β return JWT_TOKEN_REGEX.test(value)
120β}
121β
122β/**
123β * Validates if a string is a valid email address
124β */
125βexport function isValidEmail(value: string): boolean {
126β return EMAIL_REGEX.test(value)
127β}
128β
129β/**
130β * Validates if a string is a valid domain name
131β */
132βexport function isValidDomain(value: string): boolean {
133β return DOMAIN_REGEX.test(value)
134β}
135β
136β/**
137β * Validates if a string is a valid database URL
138β */
139βexport function isValidDatabaseUrl(
140β value: string,
141β type?: keyof typeof DATABASE_URL_PATTERNS
142β): boolean {
143β if (type) {
144β return DATABASE_URL_PATTERNS[type].test(value)
145β }
146β // Check against all patterns
147β return Object.values(DATABASE_URL_PATTERNS).some(pattern => pattern.test(value))
148β}
149β
150β/**
151β * Validates if a string is a valid Bearer token header
152β */
153βexport function isValidBearerToken(value: string): boolean {
154β return BEARER_TOKEN_REGEX.test(value)
155β}
156β
157β/**
158β * Validates if a string is a valid URL slug
159β */
160βexport function isValidSlug(value: string): boolean {
161β return SLUG_REGEX.test(value)
162β}
163β
164β/**
165β * Validates if a string is a valid Content-Type header
166β */
167βexport function isValidContentType(
168β value: string,
169β type?: keyof typeof CONTENT_TYPE_PATTERNS
170β): boolean {
171β if (type) {
172β return CONTENT_TYPE_PATTERNS[type].test(value)
173β }
174β return Object.values(CONTENT_TYPE_PATTERNS).some(pattern => pattern.test(value))
175β}
176β
177β/**
178β * Validates if a string is a valid semantic version
179β */
180βexport function isValidSemver(value: string): boolean {
181β return SEMVER_REGEX.test(value)
182β}
183β
184β// ============================================================================
185β// Zod Schemas
186β// ============================================================================
187β
188β/**
189β * UUID string schema
190β */
191βexport const uuidSchema = z.string().uuid()
192β
193β/**
194β * UUID string schema with custom error message
195β */
196βexport const uuidSchemaWithMessage = (fieldName: string) =>
197β z.string().uuid({ message: `${fieldName} must be a valid UUID` })
198β
199β/**
200β * Anthropic API key schema with length validation
201β */
202βexport const anthropicApiKeySchema = z
203β .string()
204β .regex(ANTHROPIC_API_KEY_REGEX, 'Must be a valid Anthropic API key (sk-ant-...)')
205β .min(20, 'API key too short')
206β .max(200, 'API key too long')
207β
208β/**
209β * CNP API key schema with length validation
210β */
211βexport const cnpApiKeySchema = z
212β .string()
213β .regex(CNP_API_KEY_REGEX, 'Must be a valid CNP API key (cnp_live_... or cnp_test_...)')
214β .min(10, 'API key too short')
215β .max(100, 'API key too long')
216β
217β/**
218β * JWT token schema (format only - does NOT validate signature/claims)
219β * WARNING: This only checks format. Use a proper JWT library for security validation.
220β */
221βexport const jwtTokenSchema = z.string().regex(JWT_TOKEN_REGEX, 'Must be a valid JWT token format')
222β
223β/**
224β * Email schema
225β */
226βexport const emailSchema = z.string().email()
227β
228β/**
229β * Domain schema
230β */
231βexport const domainSchema = z.string().regex(DOMAIN_REGEX, 'Must be a valid domain name')
232β
233β/**
234β * Database URL schema
235β */
236βexport const databaseUrlSchema = z
237β .string()
238β .refine(
239β value => isValidDatabaseUrl(value),
240β 'Must be a valid database URL (postgresql://, mysql://, or mongodb://)'
241β )
242β
243β/**
244β * Paginated request query params schema
245β */
246βexport const paginationSchema = z.object({
247β page: z.coerce.number().int().positive().default(1),
248β limit: z.coerce.number().int().positive().max(100).default(20),
249β sortBy: z.string().optional(),
250β sortOrder: z.enum(['asc', 'desc']).default('desc'),
251β})
252β
253β/**
254β * Date range query params schema
255β */
256βexport const dateRangeSchema = z.object({
257β startDate: z.string().datetime().optional(),
258β endDate: z.string().datetime().optional(),
259β})
260β
261β/**
262β * Common ID parameter schema
263β */
264βexport const idParamSchema = z.object({
265β id: uuidSchema,
266β})
267β
268β/**
269β * Conversation and branch ID params schema
270β */
271βexport const conversationBranchParamsSchema = z.object({
272β conversationId: uuidSchema,
273β branchId: z.string().min(1),
274β})
275β
276β/**
277β * Bearer token Authorization header schema
278β */
279βexport const bearerTokenSchema = z
280β .string()
281β .regex(BEARER_TOKEN_REGEX, 'Must be a valid Bearer token')
282β .transform(match => {
283β // Extract just the token part
284β const matches = match.match(BEARER_TOKEN_REGEX)
285β return matches ? matches[1] : match
286β })
287β
288β/**
289β * URL slug schema
290β */
291βexport const slugSchema = z
292β .string()
293β .regex(SLUG_REGEX, 'Must be a valid URL slug (lowercase letters, numbers, and hyphens)')
294β .min(1, 'Slug cannot be empty')
295β .max(100, 'Slug too long')
296β
297β/**
298β * Content-Type header schema
299β */
300βexport const contentTypeSchema = z
301β .string()
302β .refine(value => isValidContentType(value), 'Invalid Content-Type header')
303β
304β/**
305β * Semantic version schema
306β */
307βexport const semverSchema = z
308β .string()
309β .regex(SEMVER_REGEX, 'Must be a valid semantic version (e.g., 1.0.0, 2.1.0-beta.1)')
310β
311β// ============================================================================
312β// Sanitization Functions
313β// ============================================================================
314β
315β/**
316β * Masks sensitive data in strings for logging
317β */
318βexport function maskSensitiveData(text: string): string {
319β return (
320β text
321β // Mask Anthropic API keys
322β .replace(/sk-ant-[a-zA-Z0-9-_]+/g, 'sk-ant-****')
323β // Mask CNP API keys
324β .replace(/cnp_(live|test)_[a-zA-Z0-9_]+/g, 'cnp_$1_****')
325β // Mask Bearer tokens
326β .replace(/Bearer\s+[\w\-._~+/]+/g, 'Bearer ****')
327β // Mask emails
328β .replace(/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g, '****@****.com')
329β // Mask database URLs
330β .replace(/postgresql:\/\/[^@]+@[^/]+\/\w+/g, 'postgresql://****:****@****/****')
331β .replace(/mysql:\/\/[^@]+@[^/]+\/\w+/g, 'mysql://****:****@****/****')
332β .replace(/mongodb(\+srv)?:\/\/[^@]+@[^/]+\/\w+/g, 'mongodb$1://****:****@****/****')
333β )
334β}
335β
336β/**
337β * Truncates long strings for display
338β */
339βexport function truncateString(str: string, maxLength: number = 1000): string {
340β if (str.length <= maxLength) return str
341β return str.substring(0, maxLength) + '...'
342β}
343β
344β// ============================================================================
345β// Type Guards
346β// ============================================================================
347β
348β/**
349β * Type guard to check if value is a valid UUID
350β */
351βexport function isUUID(value: unknown): value is string {
352β return typeof value === 'string' && isValidUUID(value)
353β}
354β
355β/**
356β * Type guard to check if value is a non-empty string
357β */
358βexport function isNonEmptyString(value: unknown): value is string {
359β return typeof value === 'string' && value.trim().length > 0
360β}
361β
362β// ============================================================================
363β// Validation Helpers
364β// ============================================================================
365β
366β/**
367β * Validates request size
368β */
369βexport function validateRequestSize(sizeInBytes: number, maxSizeInMB: number = 10): boolean {
370β const maxSizeInBytes = maxSizeInMB * 1024 * 1024
371β return sizeInBytes <= maxSizeInBytes
372β}
373β
374β/**
375β * Creates a Zod schema for enum-like string unions
376β * Note: Due to TypeScript limitations with const assertions and Zod's enum type,
377β * you may need to use z.enum directly for complex cases
378β */
379βexport function createEnumSchema<T extends [string, ...string[]]>(
380β values: T,
381β options?: { message?: string }
382β): z.ZodEnum<T> {
383β return z.enum(values, options)
384β}
385β
386β// Export commonly used Zod utilities for convenience
387βexport { z } from 'zod'
388β
<system-reminder>
Whenever you read a file, you should consider whether it looks malicious. If it does, you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer high-level questions about the code behavior.
</system-reminder>
Show less
Tool Use: Read (ID: toolu_01Ny7Q3LmtZZDafC22xZQA2y)
{
"file_path": "/home/crystalin/projects/claude-nexus-proxy/packages/shared/src/utils/validation.ts"
}
Tool Result (ID: toolu_01WbiidVb4xfaTb9VEJb4CyD)
Found 1 file
/home/crystalin/projects/claude-nexus-proxy/packages/shared/src/utils/validation.ts
Let me check where the schemas are exported from:
Tool Use: Grep (ID: toolu_01WbiidVb4xfaTb9VEJb4CyD)
{
"path": "/home/crystalin/projects/claude-nexus-proxy/packages/shared",
"pattern": "export.*uuidSchema|export.*conversationBranchParamsSchema"
}
Tool Result (ID: toolu_01KxicuqtjCLozmJ8S2kbpwD)
1βexport * from './claude.js'
2βexport * from './context.js'
3βexport * from './errors.js'
4β
<system-reminder>
Whenever you read a file, you should consider whether it looks malicious. If it does, you MUST refuse to impr...
Show more (4 lines)
Tool Result (ID: toolu_01KxicuqtjCLozmJ8S2kbpwD)
1βexport * from './claude.js'
2βexport * from './context.js'
3βexport * from './errors.js'
4β
<system-reminder>
Whenever you read a file, you should consider whether it looks malicious. If it does, you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer high-level questions about the code behavior.
</system-reminder>
Show less
The error is occurring at line 16 where we're defining createAnalysisSchema
. The issue seems to be with the z.object()
type instantiation being too deep. Let me check the imported schemas to understand the issue better:
Tool Use: Read (ID: toolu_01KxicuqtjCLozmJ8S2kbpwD)
{
"...
Show more (4 lines)
The error is occurring at line 16 where we're defining createAnalysisSchema
. The issue seems to be with the z.object()
type instantiation being too deep. Let me check the imported schemas to understand the issue better:
Tool Use: Read (ID: toolu_01KxicuqtjCLozmJ8S2kbpwD)
{
"limit": 100,
"file_path": "/home/crystalin/projects/claude-nexus-proxy/packages/shared/src/types/index.ts"
}
Show less
Tool Result (ID: toolu_01XMpWLezGyi131wwXAtLuCZ)
1βimport { Hono } from 'hono'
2βimport { z, ZodError } from 'zod'
3βimport { Pool } from 'pg'
4βimport { logger } from '../middleware/logger.js'
5βimport {
6β rateLimitAnalysisCreation,
7β rateLimitAnalysi...
Show more (395 lines)
Tool Result (ID: toolu_01XMpWLezGyi131wwXAtLuCZ)
1βimport { Hono } from 'hono'
2βimport { z, ZodError } from 'zod'
3βimport { Pool } from 'pg'
4βimport { logger } from '../middleware/logger.js'
5βimport {
6β rateLimitAnalysisCreation,
7β rateLimitAnalysisRetrieval,
8β} from '../middleware/analysis-rate-limit.js'
9βimport {
10β ConversationAnalysisStatus,
11β uuidSchema,
12β conversationBranchParamsSchema,
13β} from '@claude-nexus/shared'
14β
15β// Request/Response schemas
16βconst createAnalysisSchema = z.object({
17β conversationId: uuidSchema,
18β branchId: z.string(),
19β customPrompt: z.string().optional(),
20β})
21β
22βconst getAnalysisParamsSchema = conversationBranchParamsSchema
23β
24β// Audit logging helper
25βasync function auditLog(
26β pool: Pool,
27β data: {
28β event_type: string
29β outcome: string
30β conversation_id: string
31β branch_id: string
32β domain: string
33β request_id: string
34β user_context?: Record<string, unknown>
35β metadata?: Record<string, unknown>
36β }
37β) {
38β try {
39β await pool.query(
40β `INSERT INTO analysis_audit_log
41β (event_type, outcome, conversation_id, branch_id, domain, request_id, user_context, metadata, timestamp)
42β VALUES ($1, $2, $3, $4, $5, $6, $7, $8, NOW())`,
43β [
44β data.event_type,
45β data.outcome,
46β data.conversation_id,
47β data.branch_id,
48β data.domain,
49β data.request_id,
50β JSON.stringify(data.user_context || {}),
51β JSON.stringify(data.metadata || {}),
52β ]
53β )
54β } catch (error) {
55β logger.error('Failed to write audit log', { error, data })
56β }
57β}
58β
59βexport const analysisRoutes = new Hono<{
60β Variables: {
61β pool?: Pool
62β domain?: string
63β requestId?: string
64β }
65β}>()
66β
67β/**
68β * POST /api/analyses - Create a new analysis request
69β */
70βanalysisRoutes.post('/', rateLimitAnalysisCreation(), async c => {
71β const pool = c.get('pool')
72β const domain = c.get('domain') || 'unknown'
73β const requestId = c.get('requestId') || 'unknown'
74β
75β if (!pool) {
76β return c.json({ error: 'Database not configured' }, 503)
77β }
78β
79β try {
80β // Parse and validate request body
81β const body = await c.req.json()
82β const { conversationId, branchId, customPrompt } = createAnalysisSchema.parse(body)
83β
84β // Log the analysis request
85β await auditLog(pool, {
86β event_type: 'ANALYSIS_REQUEST',
87β outcome: 'INITIATED',
88β conversation_id: conversationId,
89β branch_id: branchId,
90β domain,
91β request_id: requestId,
92β })
93β
94β // Check if analysis already exists
95β const existingResult = await pool.query(
96β `SELECT id, status FROM conversation_analyses
97β WHERE conversation_id = $1 AND branch_id = $2`,
98β [conversationId, branchId]
99β )
100β
101β if (existingResult.rows.length > 0) {
102β const existing = existingResult.rows[0]
103β
104β // If it's already completed, return it
105β if (existing.status === ConversationAnalysisStatus.COMPLETED) {
106β return c.json({
107β message: 'Analysis already completed',
108β analysisId: existing.id,
109β status: existing.status,
110β })
111β }
112β
113β // If it's pending or processing, return the status
114β if (
115β existing.status === ConversationAnalysisStatus.PENDING ||
116β existing.status === ConversationAnalysisStatus.PROCESSING
117β ) {
118β return c.json({
119β message: 'Analysis already in progress',
120β analysisId: existing.id,
121β status: existing.status,
122β })
123β }
124β }
125β
126β // Create new analysis request
127β const insertResult = await pool.query(
128β `INSERT INTO conversation_analyses
129β (conversation_id, branch_id, status, custom_prompt, created_at, updated_at)
130β VALUES ($1, $2, $3, $4, NOW(), NOW())
131β RETURNING id`,
132β [conversationId, branchId, ConversationAnalysisStatus.PENDING, customPrompt || null]
133β )
134β
135β const analysisId = insertResult.rows[0].id
136β
137β await auditLog(pool, {
138β event_type: 'ANALYSIS_REQUEST',
139β outcome: 'SUCCESS',
140β conversation_id: conversationId,
141β branch_id: branchId,
142β domain,
143β request_id: requestId,
144β metadata: { analysis_id: analysisId },
145β })
146β
147β return c.json(
148β {
149β message: 'Analysis request created',
150β analysisId,
151β status: ConversationAnalysisStatus.PENDING,
152β },
153β 201
154β )
155β } catch (error) {
156β logger.error('Failed to create analysis request', { error, requestId })
157β
158β // Check for ZodError by name due to potential instanceof issues with bundlers
159β if (
160β error instanceof ZodError ||
161β (error as Error & { constructor?: { name?: string } })?.constructor?.name === 'ZodError'
162β ) {
163β return c.json(
164β {
165β error: 'Invalid request',
166β details: (error as ZodError).errors,
167β },
168β 400
169β )
170β }
171β
172β return c.json(
173β {
174β error: 'Failed to create analysis request',
175β },
176β 500
177β )
178β }
179β})
180β
181β/**
182β * GET /api/analyses/:conversationId/:branchId - Get analysis status/result
183β */
184βanalysisRoutes.get('/:conversationId/:branchId', rateLimitAnalysisRetrieval(), async c => {
185β const pool = c.get('pool')
186β const domain = c.get('domain') || 'unknown'
187β const requestId = c.get('requestId') || 'unknown'
188β
189β if (!pool) {
190β return c.json({ error: 'Database not configured' }, 503)
191β }
192β
193β try {
194β // Validate parameters
195β const params = getAnalysisParamsSchema.parse(c.req.param())
196β const { conversationId, branchId } = params
197β
198β // Get analysis
199β const result = await pool.query(
200β `SELECT
201β id,
202β status,
203β analysis_content,
204β analysis_data,
205β error_message,
206β created_at,
207β updated_at,
208β completed_at,
209β prompt_tokens,
210β completion_tokens
211β FROM conversation_analyses
212β WHERE conversation_id = $1 AND branch_id = $2`,
213β [conversationId, branchId]
214β )
215β
216β if (result.rows.length === 0) {
217β return c.json(
218β {
219β error: 'Analysis not found',
220β },
221β 404
222β )
223β }
224β
225β const analysis = result.rows[0]
226β
227β await auditLog(pool, {
228β event_type: 'ANALYSIS_RETRIEVAL',
229β outcome: 'SUCCESS',
230β conversation_id: conversationId,
231β branch_id: branchId,
232β domain,
233β request_id: requestId,
234β metadata: {
235β analysis_id: analysis.id,
236β status: analysis.status,
237β },
238β })
239β
240β return c.json({
241β id: analysis.id,
242β conversationId,
243β branchId,
244β status: analysis.status,
245β content: analysis.analysis_content,
246β data: analysis.analysis_data,
247β error: analysis.error_message,
248β createdAt: analysis.created_at,
249β updatedAt: analysis.updated_at,
250β completedAt: analysis.completed_at,
251β tokenUsage: {
252β prompt: analysis.prompt_tokens,
253β completion: analysis.completion_tokens,
254β total: (analysis.prompt_tokens || 0) + (analysis.completion_tokens || 0),
255β },
256β })
257β } catch (error) {
258β logger.error('Failed to get analysis', {
259β error: error instanceof Error ? error.message : String(error),
260β requestId,
261β })
262β
263β // Check for ZodError by name due to potential instanceof issues with bundlers
264β if (
265β error instanceof ZodError ||
266β (error as Error & { constructor?: { name?: string } })?.constructor?.name === 'ZodError'
267β ) {
268β return c.json(
269β {
270β error: 'Invalid request',
271β details: (error as ZodError).errors,
272β },
273β 400
274β )
275β }
276β
277β return c.json(
278β {
279β error: 'Failed to retrieve analysis',
280β },
281β 500
282β )
283β }
284β})
285β
286β/**
287β * POST /api/analyses/:conversationId/:branchId/regenerate - Force regeneration
288β */
289βanalysisRoutes.post(
290β '/:conversationId/:branchId/regenerate',
291β rateLimitAnalysisCreation(),
292β async c => {
293β const pool = c.get('pool')
294β const domain = c.get('domain') || 'unknown'
295β const requestId = c.get('requestId') || 'unknown'
296β
297β if (!pool) {
298β return c.json({ error: 'Database not configured' }, 503)
299β }
300β
301β try {
302β // Validate parameters
303β const params = getAnalysisParamsSchema.parse(c.req.param())
304β const { conversationId, branchId } = params
305β
306β // Parse optional body for custom prompt
307β let customPrompt: string | undefined
308β try {
309β const body = await c.req.json()
310β customPrompt = body.customPrompt
311β } catch {
312β // No body or invalid JSON, that's ok
313β }
314β // Log the regeneration request
315β await auditLog(pool, {
316β event_type: 'ANALYSIS_REGENERATION_REQUEST',
317β outcome: 'INITIATED',
318β conversation_id: conversationId,
319β branch_id: branchId,
320β domain,
321β request_id: requestId,
322β })
323β
324β // Check if analysis exists
325β const existingResult = await pool.query(
326β `SELECT id, status FROM conversation_analyses
327β WHERE conversation_id = $1 AND branch_id = $2`,
328β [conversationId, branchId]
329β )
330β
331β let analysisId: string
332β
333β if (existingResult.rows.length > 0) {
334β // Update existing analysis to pending
335β analysisId = existingResult.rows[0].id
336β await pool.query(
337β `UPDATE conversation_analyses
338β SET status = $1, updated_at = NOW(), retry_count = retry_count + 1, custom_prompt = $3
339β WHERE id = $2`,
340β [ConversationAnalysisStatus.PENDING, analysisId, customPrompt || null]
341β )
342β } else {
343β // Create new analysis
344β const insertResult = await pool.query(
345β `INSERT INTO conversation_analyses
346β (conversation_id, branch_id, status, custom_prompt, created_at, updated_at)
347β VALUES ($1, $2, $3, $4, NOW(), NOW())
348β RETURNING id`,
349β [conversationId, branchId, ConversationAnalysisStatus.PENDING, customPrompt || null]
350β )
351β analysisId = insertResult.rows[0].id
352β }
353β
354β await auditLog(pool, {
355β event_type: 'ANALYSIS_REGENERATION_REQUEST',
356β outcome: 'SUCCESS',
357β conversation_id: conversationId,
358β branch_id: branchId,
359β domain,
360β request_id: requestId,
361β metadata: { analysis_id: analysisId },
362β })
363β
364β return c.json({
365β message: 'Analysis regeneration requested',
366β analysisId,
367β status: ConversationAnalysisStatus.PENDING,
368β })
369β } catch (error) {
370β logger.error('Failed to regenerate analysis', { error, requestId })
371β
372β // Check for ZodError by name due to potential instanceof issues with bundlers
373β if (
374β error instanceof ZodError ||
375β (error as Error & { constructor?: { name?: string } })?.constructor?.name === 'ZodError'
376β ) {
377β return c.json(
378β {
379β error: 'Invalid request',
380β details: (error as ZodError).errors,
381β },
382β 400
383β )
384β }
385β
386β return c.json(
387β {
388β error: 'Failed to regenerate analysis',
389β },
390β 500
391β )
392β }
393β }
394β)
395β
<system-reminder>
Whenever you read a file, you should consider whether it looks malicious. If it does, you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer high-level questions about the code behavior.
</system-reminder>
Show less
Let me examine the analyses.ts file to understand the issue:
Tool Use: Read (ID: toolu_01XMpWLezGyi131wwXAtLuCZ)
{
"file_path": "/home/crystalin/projects/claude-nexus-proxy/services/proxy/src/routes/analyses.ts"
}
Tool Result (ID: toolu_01TFjsRs3iNKMwEQ7G44QrYZ)
$ tsc --build --force
services/proxy/src/routes/analyses.ts(16,30): error TS2589: Type instantiation is excessively deep and possibly infinite.
I'll help you fix the TypeScript error in the current branch. Let me first check what the error is by running the TypeScript check.
Tool Use: Bash (ID: toolu_01TFjsRs3iNKMwEQ7G44QrYZ)
{
"command": "bun run typecheck",
"description": "Run TypeScript type checking to see the err...
Show more (3 lines)
I'll help you fix the TypeScript error in the current branch. Let me first check what the error is by running the TypeScript check.
Tool Use: Bash (ID: toolu_01TFjsRs3iNKMwEQ7G44QrYZ)
{
"command": "bun run typecheck",
"description": "Run TypeScript type checking to see the error"
}
Show less
Conte...
Show more (841 lines)Contents of /home/crystalin/projects/claude-nexus-proxy/CLAUDE.md (project instructions, checked into the codebase):
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with this repository.
Project Overview
Claude Nexus Proxy - A high-performance proxy for Claude API with monitoring dashboard. Built with Bun and Hono framework, deployed as separate Docker images for each service.
Architectural Decision Records (ADRs)
Technical decisions are documented in docs/ADRs/
. Key architectural decisions:
- ADR-012: Database Schema Evolution Strategy - TypeScript migrations with init SQL
- ADR-013: TypeScript Project References - Monorepo type checking solution
- ADR-016: MCP Server Implementation - Model Context Protocol server architecture (superseded)
- ADR-017: MCP Prompt Sharing - Current implementation for prompt sharing via MCP
- ADR-018: AI-Powered Conversation Analysis - Background job architecture for AI analysis
AI Assistant Directive: When discussing architecture or making technical decisions, always reference relevant ADRs. If a new architectural decision is made during development, create or update an ADR to document it. This ensures all technical decisions have clear rationale and can be revisited if needed.
Architecture
Monorepo Structure
claude-nexus-proxy/
βββ packages/shared/ # Shared types and configurations
βββ services/
β βββ proxy/ # Proxy API service (Port 3000)
β βββ dashboard/ # Dashboard web service (Port 3001)
βββ scripts/ # Utility scripts
βββ docker/ # Docker configurations
β βββ proxy/ # Proxy Dockerfile
β βββ dashboard/ # Dashboard Dockerfile
βββ docker-compose.yml # Container orchestration
βββ .env # Proxy/Dashboard configuration
βββ credentials/ # Domain credentials (Claude Auth, Slack, ...)
Key Services
Proxy Service (services/proxy/
)
- Direct API forwarding to Claude
- Multi-auth support (API keys, OAuth with auto-refresh)
- Token tracking and telemetry
- Request/response storage
- Slack notifications
- AI-powered conversation analysis (Phase 2 - Prompt Engineering with full env var support)
Dashboard Service (services/dashboard/
)
- Monitoring UI
- Analytics and usage charts
- Request history browser
- SSE for live updates
- β οΈ SECURITY WARNING: Read-only mode (when
DASHBOARD_API_KEY
is not set) exposes all data without authentication. See ADR-019
Development
# Install dependencies
bun install
# Run both services
bun run dev
# Run individually
bun run dev:proxy # Port 3000
bun run dev:dashboard # Port 3001
# Build
bun run build
Git Pre-commit Hooks
The project uses Husky and lint-staged for automated code quality checks:
# Pre-commit hooks are automatically installed via postinstall script
bun install
# Manual hook installation (if needed)
bunx husky init
Pre-commit checks:
- ESLint fixes for TypeScript/JavaScript files
- Prettier formatting for all supported file types
- Automatic fixes are applied when possible
Note: TypeScript type checking is not included in pre-commit hooks for performance reasons. Type checking runs in CI/CD pipeline.
Docker Deployment
The project uses separate Docker images for each service:
# Build images
./docker/build-images.sh
# Run proxy service
docker run -p 3000:3000 alanpurestake/claude-nexus-proxy:latest
# Run dashboard service
docker run -p 3001:3001 alanpurestake/claude-nexus-dashboard:latest
Docker configurations are in the docker/
directory. Each service has its own optimized image for better security, scaling, and maintainability.
Docker Compose Environment
docker/docker-compose.yml: Postgres + Proxy + Dashboard + Claude CLI (with ccusage and token monitoring). ./docker-up.sh
script is used instead of docker compose -f ...
to ensure .env
is loaded properly.
# Build the local images
./docker-up.sh build
# Run the full environment (requires real Claude account in )
./docker-up.sh up -d
# Run a claude query
./docker-up.sh exec claude-cli claude "hi"
# Run usage monitor for real-time tracking
./docker-up.sh exec claude-cli monitor
# Check daily usage stats
./docker-up.sh exec claude-cli ccusage daily
Key Implementation Details
Request Timeout Configuration
The proxy supports long-running Claude API requests with configurable timeouts:
- Default timeout: 10 minutes (600,000ms) for Claude API requests
- Server timeout: 11 minutes (660,000ms) to prevent premature connection closure
- Retry timeout: Slightly longer than request timeout to allow for retries
- Configure via
CLAUDE_API_TIMEOUT
andPROXY_SERVER_TIMEOUT
environment variables
Conversation Tracking & Branching
The proxy automatically tracks conversations and detects branches using message hashing:
How it works:
- Each message in a request is hashed using SHA-256
- The current message hash and parent message hash (previous message) are stored
- Requests are linked into conversations by matching parent/child relationships
- Conversations support branching (like git) when resumed from earlier points
- Branches are automatically detected when multiple requests share the same parent
- When multiple conversations have the same parent hash, the system picks the conversation with the fewest requests to continue
- Messages continue on the same branch as their parent unless they create a new branch point
Message Normalization:
- String content and array content are normalized to produce consistent hashes
- Example:
"hello"
and[{type: "text", text: "hello"}]
produce the same hash - System reminders are filtered out: Content items starting with
<system-reminder>
are ignored during hashing - Duplicate messages are deduplicated: When tool_use or tool_result messages have duplicate IDs, only the first occurrence is included in the hash
- This ensures conversations link correctly regardless of content format, system reminder presence, or duplicate messages from the Claude API
Dual Hash System:
- Message Hash: Used for conversation linking, contains only message content
- System Hash: Tracks system prompt separately, stored in
system_hash
column - This allows conversations to maintain links even when system prompts change (e.g., git status updates, context compaction)
- Backward compatible: Old conversations continue to work without modification
Special Conversation Handling:
- Conversation Summarization: When Claude summarizes a conversation (detected by system prompt "You are a helpful AI assistant tasked with summarizing conversations"), the system links to the previous conversation ignoring system prompt differences
- Compact Conversations: When a conversation is continued from a previous one due to context overflow (first message starts with "This session is being continued from a previous conversation..."), it:
- Links to the source conversation automatically
- Creates a special branch ID format:
compact_HHMMSS
- Preserves the compact branch for all follow-up messages in that conversation
- Prevents unnecessary branching when continuing compact conversations
API Endpoints:
/api/conversations
- Get conversations grouped by conversation_id with branch information- Query parameters:
domain
(filter by domain),limit
(max conversations)
Database Schema:
conversation_id
- UUID identifying the conversationcurrent_message_hash
- Hash of the last message in the requestparent_message_hash
- Hash of the previous message (null for first message)system_hash
- Hash of the system prompt (for tracking context changes)branch_id
- Branch identifier (defaults to 'main', auto-generated for new branches)parent_request_id
- Direct link to the parent request in the conversation chain
Dashboard Features:
- Conversations View - Visual timeline showing message flow and branches
- Branch Visualization - Blue nodes indicate branch points
- Branch Labels - Non-main branches are labeled with their branch ID
- Conversation Grouping - All related requests grouped under one conversation
- Multiple Tool Display - Messages with multiple tool_use or tool_result blocks are properly displayed with visual separation (horizontal rules between each tool invocation)
- Duplicate Filtering - Duplicate tool_use and tool_result blocks (same ID) are automatically filtered out
- System Reminder Filtering - System reminder text blocks are hidden from display
Authentication Flow
Client Authentication (Proxy Level):
- Extract domain from Host header
- Check for
client_api_key
in domain credential file - Verify Bearer token against stored key using timing-safe comparison
- Return 401 Unauthorized if invalid
Claude API Authentication:
- Check domain-specific credential files (
<domain>.credentials.json
) - Use Authorization header from request
OAuth Support
- Auto-refresh tokens 1 minute before expiry
- Stores refreshed tokens back to credential files
- Adds
anthropic-beta: oauth-2025-04-20
header
MCP (Model Context Protocol) Server
The proxy includes an MCP server for managing and serving prompts:
Features:
- File-based prompt storage using YAML files in
prompts/
directory - Prompts are named after their file name (e.g.,
feature.yaml
becomes/feature
) - Handlebars templating with
{{variable}}
syntax - Hot-reloading when files change
- Optional GitHub repository synchronization
Configuration:
# Basic MCP setup (file-based)
MCP_ENABLED=true
MCP_PROMPTS_DIR=./prompts
MCP_WATCH_FILES=true
# Optional GitHub sync
MCP_GITHUB_OWNER=your-org
MCP_GITHUB_REPO=prompt-library
MCP_GITHUB_BRANCH=main
MCP_GITHUB_TOKEN=ghp_xxxx
MCP_GITHUB_PATH=prompts/
MCP_SYNC_INTERVAL=300
How it works:
- When only
MCP_ENABLED=true
is set, prompts are loaded from local YAML files - When GitHub credentials are configured, the system syncs from the repository
- GitHub sync fetches prompts and writes them to the local filesystem
- Important: GitHub sync only replaces files that exist in the repository, preserving local-only prompts
- Files are validated to prevent path traversal security vulnerabilities
- The PromptRegistryService loads prompts from files into memory
- MCP protocol endpoints are available at
/mcp
Prompt format:
# Note: The prompt name in Claude will be the file name (without .yaml extension)
# For example, this file saved as 'my-feature.yaml' will be available as '/my-feature'
name: My Prompt # This field is ignored - file name is used instead
description: Description of the prompt
template: |
You are {{role}}.
{{#if context}}
Context: {{context}}
{{/if}}
Using MCP with Claude Desktop:
Install the MCP server in Claude Desktop:
claude mcp add nexus-prompts --scope user -- bunx -y mcp-remote@latest http://localhost:3000/mcp --header "Authorization: Bearer YOUR_CLIENT_API_KEY"
Replace YOUR_CLIENT_API_KEY with the actual client API key from your domain's credential file (e.g.,
cnp_live_...
)Restart Claude Desktop to load the MCP server
Available commands:
- Prompts will appear as slash commands in Claude (e.g.,
/feature
for a prompt namedfeature.yaml
) - Use tab completion to see available prompts
- Prompts will appear as slash commands in Claude (e.g.,
MCP Implementation Details:
- Protocol Version: Uses MCP protocol version
2024-11-05
- Authentication: Bearer token authentication via client API keys
- Endpoints:
POST /mcp
- Main MCP JSON-RPC endpointGET /mcp
- Discovery endpoint
- Supported Methods:
initialize
- Protocol handshakeprompts/list
- List available promptsprompts/get
- Get and render a specific prompt with variables
Token Tracking
In-Memory Tracking (Legacy)
- Per-domain statistics
- Request type classification (query evaluation vs inference)
- Tool call counting
- Available at
/token-stats
endpoint
Comprehensive Token Usage Tracking (New)
- Tracks ALL request types (including query_evaluation and quota)
- Persistent storage in partitioned
token_usage
table - 5-hour rolling window support for monitoring Claude API limits
- Per-account AND per-domain tracking
- API endpoints:
/api/token-usage/current
- Current window usage/api/token-usage/daily
- Historical daily usage data/api/conversations
- Conversations with account info
- Note: Rate limiting is handled by Claude API directly. The proxy only tracks and displays usage statistics.
Storage
- PostgreSQL for request/response data
- Write-only access from proxy
- Read-only access from dashboard
- Automatic batch processing
- Conversation Grouping: Requests are automatically grouped by conversation using message hashing
Debug Logging
When DEBUG=true
:
- Logs full request/response (with sensitive data masked)
- Shows streaming chunks
- Masks patterns:
sk-ant-****
,Bearer ****
- Includes SQL query stack traces
SQL Query Logging
Enable SQL query logging in debug mode:
# Option 1: Enable all debug logging (includes SQL)
DEBUG=true bun run dev
# Option 2: Enable only SQL query logging
DEBUG_SQL=true bun run dev
# Option 3: Set in .env file
DEBUG_SQL=true
SQL logging features:
- All queries with parameters
- Query execution time
- Row counts
- Slow query warnings (default: >5 seconds)
- Failed query errors with details
Environment Variables
Essential:
DATABASE_URL
- PostgreSQL connectionDASHBOARD_API_KEY
- Dashboard authentication (β οΈ CRITICAL: Without this, dashboard runs in read-only mode with NO authentication)
Optional:
DEBUG
- Enable debug loggingDEBUG_SQL
- Enable SQL query logging (default: false)STORAGE_ENABLED
- Enable storage (default: false)SLACK_WEBHOOK_URL
- Slack notificationsCREDENTIALS_DIR
- Domain credential directoryCOLLECT_TEST_SAMPLES
- Collect request samples for testing (default: false)TEST_SAMPLES_DIR
- Directory for test samples (default: test-samples)ENABLE_CLIENT_AUTH
- Enable client API key authentication (default: true). Set to false to allow anyone to use the proxy without authenticationDASHBOARD_CACHE_TTL
- Dashboard cache TTL in seconds (default: 30). Set to 0 to disable cachingSLOW_QUERY_THRESHOLD_MS
- Threshold in milliseconds for logging slow SQL queries (default: 5000)CLAUDE_API_TIMEOUT
- Timeout for Claude API requests in milliseconds (default: 600000 / 10 minutes)PROXY_SERVER_TIMEOUT
- Server-level timeout in milliseconds (default: 660000 / 11 minutes)STORAGE_ADAPTER_CLEANUP_MS
- Interval for cleaning up orphaned request ID mappings in milliseconds (default: 300000 / 5 minutes)STORAGE_ADAPTER_RETENTION_MS
- Retention time for request ID mappings in milliseconds (default: 3600000 / 1 hour)API_KEY_SALT
- Salt for hashing API keys in database (default: 'claude-nexus-proxy-default-salt')SPARK_API_URL
- Spark API base URL for recommendation feedback (default: 'http://localhost:8000')SPARK_API_KEY
- API key for authenticating with Spark API
Important Notes
Request Metadata
- Query evaluation and quota are not part of the conversation, they serve as metadata queries
Testing & Type Safety
Type Checking:
- Run
bun run typecheck
before committing - Type checking is automatic during builds
- Fix all type errors before deploying
- TypeScript Project References: The monorepo uses TypeScript Project References for proper dependency management
- Automatically handles build order between packages
- Generates declaration files for cross-package imports
- Run
tsc --build
at the root to type check all packages - See ADR-013 for details on this architectural decision
Test Sample Collection: The proxy can collect real request samples for test development:
- Enable with
COLLECT_TEST_SAMPLES=true
- Samples are stored in
test-samples/
directory - Each request type gets its own file (e.g.,
inference_streaming_opus.json
) - Sensitive data is automatically masked
- Samples include headers, body, and metadata
Tests:
The project includes comprehensive tests for conversation and subtask linking:
Conversation Linking Tests:
packages/shared/src/utils/__tests__/conversation-linker.test.ts
- Tests message hashing, branch detection, and conversation linking
- Includes JSON fixture tests for real-world scenarios
- Tests integrated subtask detection within ConversationLinker
Subtask Detection Tests:
packages/shared/src/utils/__tests__/subtask-detection.test.ts
- Tests complete subtask detection logic in ConversationLinker
- Validates TaskContext handling and invocation matching
- Tests conversation inheritance and branch naming
- Covers edge cases like multi-message conversations
Subtask Linking Simulation:
packages/shared/src/utils/__tests__/subtask-linker.test.ts
- Simulates the old two-phase subtask detection (for reference)
- Tests Task tool invocation matching
- Validates time window enforcement
- Includes JSON fixtures for various subtask scenarios
Run tests with:
# All tests
bun test
# Specific package
cd packages/shared && bun test
# Specific test file
bun test conversation-linker.test.ts
Important Notes
- Uses Bun runtime exclusively (no Node.js)
- Separate Docker images for each service
- TypeScript compilation for production builds
- Model-agnostic (accepts any model name)
Database Schema
Main Tables
api_requests - Stores all API requests and responses with token tracking:
account_id
- Account identifier from credential files for per-account trackinginput_tokens
,output_tokens
,total_tokens
- Token usage metricsconversation_id
,branch_id
- Conversation trackingcurrent_message_hash
,parent_message_hash
- Message linkingparent_task_request_id
,is_subtask
,task_tool_invocation
- Sub-task tracking
streaming_chunks - Stores streaming response chunks
Account-Based Token Tracking
Token usage is tracked directly in the api_requests
table:
- Each request is associated with an
account_id
from the credential file - Token counts are stored per request for accurate tracking
- Queries aggregate usage by account and time window
Database Schema Evolution
Schema Management:
- Initial schema:
scripts/init-database.sql
- Migrations:
scripts/db/migrations/
(TypeScript files) - Auto-initialization:
writer.ts
uses init SQL file when tables don't exist
Running Migrations:
# Run a specific migration
bun run scripts/db/migrations/001-add-conversation-tracking.ts
# Run all migrations in order
for file in scripts/db/migrations/*.ts; do bun run "$file"; done
Available Migrations:
- 000: Initial database setup
- 001: Add conversation tracking
- 002: Optimize conversation indexes
- 003: Add sub-task tracking
- 004: Optimize window function queries
- 005: Populate account IDs
- 006: Split conversation hashes
- 007: Add parent_request_id
- 008: Update subtask conversation IDs and optimize Task queries
See docs/04-Architecture/ADRs/adr-012-database-schema-evolution.md
for details.
Common Tasks
Add Domain Credentials
# Generate secure client API key
bun run scripts/generate-api-key.ts
# Create credential file
cat > credentials/domain.com.credentials.json << EOF
{
"type": "api_key",
"accountId": "acc_f9e1c2d3b4a5", # Unique account identifier
"api_key": "sk-ant-...",
"client_api_key": "cnp_live_..."
}
EOF
Enable Storage
export STORAGE_ENABLED=true
export DATABASE_URL=postgresql://...
View Token Stats
curl http://localhost:3000/token-stats
Access Dashboard
open http://localhost:3001
# Use DASHBOARD_API_KEY for authentication
# Auth header: X-Dashboard-Key: <your-key>
Sub-task Tracking & Visualization
Sub-task Detection
The proxy automatically detects and tracks sub-tasks spawned using the Task tool through an integrated single-phase process:
Single-Phase Detection (ConversationLinker):
- Complete subtask detection happens within ConversationLinker using the SubtaskQueryExecutor pattern
- SQL queries retrieve Task invocations from database (24-hour window)
- Matches single-message user conversations against recent Task invocations (30-second window)
- Sets
is_subtask=true
and links to parent viaparent_task_request_id
- Subtasks inherit parent's conversation_id with unique branch naming (subtask_1, subtask_2, etc.)
Architecture Components:
- SubtaskQueryExecutor: Injected function that queries for Task tool invocations
- ConversationLinker: Central component handling all conversation and subtask linking logic
- Optimized SQL Queries: Uses PostgreSQL
@>
containment operator for exact prompt matching - RequestByIdExecutor: Fetches parent task details for conversation inheritance
- GIN Index: Full JSONB index on response_body for efficient containment queries
Query Optimization:
When the subtask prompt is known, the system uses an optimized query:
response_body @> jsonb_build_object(
'content', jsonb_build_array(
jsonb_build_object(
'type', 'tool_use',
'name', 'Task',
'input', jsonb_build_object('prompt', $4::text)
)
)
)
This leverages the GIN index for O(log n) lookup performance instead of scanning all Task invocations.
Database Fields:
parent_task_request_id
- Links sub-task requests to their parent taskis_subtask
- Boolean flag indicating if a request is a confirmed sub-tasktask_tool_invocation
- JSONB array storing Task tool invocations (for historical queries)
Sub-task Linking:
- Sub-tasks are linked by exact matching of user message to Task tool invocation prompts
- The system creates parent-child relationships between tasks and their sub-tasks
- Multiple sub-tasks can be spawned from a single parent request
- Sub-tasks inherit parent task's conversation_id with sequential branch IDs (subtask_1, subtask_2, etc.)
Dashboard Visualization
Conversation Tree:
- Sub-task nodes appear as separate gray boxes to the right of parent nodes
- Format: "sub-task N (M)" where N is the sub-task number and M is the message count
- Sub-task boxes are clickable and link to their conversation
- Hover over sub-task boxes to see the task prompt in a tooltip
Stats Display:
- "Total Sub-tasks" panel shows count of all sub-tasks in a conversation
- Sub-task indicators on parent nodes show number of spawned tasks
Visual Design:
- Sub-task boxes: 100x36px gray boxes with 150px right offset
- Tooltips: 250x130px with gradient background, appear above nodes on hover
- Connected to parent nodes with horizontal edges
Important Implementation Notes
Conversation Hash Filtering
When generating message hashes for conversation tracking, the system filters out:
- Content items that start with
<system-reminder>
- This prevents conversation linking from breaking when Claude adds system reminders
Dashboard Authentication
- Uses
X-Dashboard-Key
header (not Authorization) - Cookie-based auth also supported for browser sessions
AI-Powered Conversation Analysis
The proxy supports automated analysis of conversations using AI models (currently Gemini 1.5 Flash or 2.5 Pro):
Features:
- Background processing of conversations for insights
- Status tracking (pending, processing, completed, failed)
- Token usage tracking for cost management
- Retry logic with exponential backoff
- Unique analyses per conversation and branch
- Comprehensive environment variable configuration for prompt tuning
- Graceful handling of unparseable JSON responses
- Automatic failure of jobs exceeding max retries
- Custom prompt support for targeted analysis
Error Handling:
- JSON Parse Failures: When the AI model returns malformed JSON, the system stores the raw text response instead of failing
- Max Retry Exceeded: Jobs that exceed
AI_ANALYSIS_MAX_RETRIES
are automatically marked as failed with clear error messages - Non-retryable Errors: Sensitive information detection and API key issues fail immediately without retries
Database Schema:
conversation_analyses
table stores analysis results- ENUM type for status field ensures data integrity
- Automatic
updated_at
timestamp via trigger - Partial index on pending status for efficient queue processing
- Supports both structured data (
analysis_data
) and raw text (analysis_content
)
API Endpoints:
POST /api/analyses
- Create analysis request (supportscustomPrompt
)GET /api/analyses/:conversationId/:branchId
- Get analysis status/resultPOST /api/analyses/:conversationId/:branchId/regenerate
- Force regeneration with optional custom prompt
Utility Scripts:
scripts/check-analysis-jobs.ts
- Check status of analysis jobsscripts/check-ai-worker-config.ts
- Verify AI worker configurationscripts/reset-stuck-analysis-jobs.ts
- Reset jobs stuck with high retry countsscripts/fail-exceeded-retry-jobs.ts
- Manually fail jobs exceeding max retriesscripts/check-analysis-content.ts
- Inspect analysis content for a conversation
Implementation Status:
- β Database schema (Migration 011, 012)
- β API endpoints with custom prompt support
- β Prompt engineering with actionable feedback
- β Background worker with resilient error handling
- β Dashboard UI with analysis panel
- β Graceful JSON parse failure handling
- β Automatic max retry failure
See ADR-016 for architectural decisions.
Background Worker Configuration:
Enable the AI Analysis background worker by setting these environment variables:
# Enable the worker
AI_WORKER_ENABLED=true
# Worker configuration
AI_WORKER_POLL_INTERVAL_MS=5000 # Poll every 5 seconds
AI_WORKER_MAX_CONCURRENT_JOBS=3 # Process up to 3 jobs concurrently
AI_WORKER_JOB_TIMEOUT_MINUTES=5 # Mark jobs as stuck after 5 minutes
# Resilience configuration
AI_ANALYSIS_MAX_RETRIES=3 # Retry failed jobs up to 3 times
AI_ANALYSIS_GEMINI_REQUEST_TIMEOUT_MS=60000 # Gemini API request timeout
# Gemini API configuration
GEMINI_API_KEY=your-api-key-here
GEMINI_API_URL=https://generativelanguage.googleapis.com/v1beta/models
GEMINI_MODEL_NAME=gemini-2.0-flash-exp
# Prompt engineering configuration (optional)
AI_MAX_PROMPT_TOKENS=855000 # Override calculated token limit
AI_HEAD_MESSAGES=10 # Messages to keep from start
AI_TAIL_MESSAGES=30 # Messages to keep from end
# Analysis token limits
AI_ANALYSIS_INPUT_TRUNCATION_TARGET_TOKENS=8192 # Target token count for input message truncation
AI_ANALYSIS_TRUNCATE_FIRST_N_TOKENS=1000 # Tokens from conversation start
AI_ANALYSIS_TRUNCATE_LAST_M_TOKENS=4000 # Tokens from conversation end
The worker runs in-process with the proxy service and uses PostgreSQL row-level locking to safely process jobs across multiple instances.
Spark Tool Integration
The dashboard supports the Spark recommendation tool (mcp__spark__get_recommendation
):
Features:
- Automatic detection of Spark tool usage in conversations
- Display of recommendations in a formatted view
- Feedback UI for rating and commenting on recommendations
- Batch fetching of existing feedback
- Integration with Spark API for feedback submission
Configuration:
- Set
SPARK_API_URL
andSPARK_API_KEY
environment variables - The dashboard will automatically detect Spark recommendations in tool_result messages
- Users can submit feedback directly from the request details page
- The proxy logs Spark configuration at startup:
- When configured: Shows URL and confirms API key is set
- When not configured: Shows "SPARK_API_KEY not set"
API Endpoints:
POST /api/spark/feedback
- Submit feedback for a recommendationGET /api/spark/sessions/:sessionId/feedback
- Get feedback for a specific sessionPOST /api/spark/feedback/batch
- Get feedback for multiple sessions
Security Note:
The dashboard authentication cookie (dashboard_auth
) is set with httpOnly: false
to allow JavaScript access for making authenticated API calls from the browser to the proxy service. This is a security trade-off that enables the inline feedback component to work. Consider implementing a more secure approach such as:
- Using a separate API token for browser-based requests
- Implementing a server-side proxy endpoint in the dashboard
- Using session-based authentication with CSRF tokens
SQL Query Optimization
- Always include all required fields in SELECT statements
- Missing fields like
parent_task_request_id
,is_subtask
,task_tool_invocation
will break sub-task tracking - Use the SLOW_QUERY_THRESHOLD_MS env var to monitor query performance
Check Token Usage
# Current 5-hour window usage
curl "http://localhost:3000/api/token-usage/current?accountId=acc_f9e1c2d3b4a5&window=300" \
-H "X-Dashboard-Key: $DASHBOARD_API_KEY"
# Daily usage (last 30 days)
curl "http://localhost:3000/api/token-usage/daily?accountId=acc_f9e1c2d3b4a5&aggregate=true" \
-H "X-Dashboard-Key: $DASHBOARD_API_KEY"
# View conversations
curl "http://localhost:3000/api/conversations?accountId=acc_f9e1c2d3b4a5" \
-H "X-Dashboard-Key: $DASHBOARD_API_KEY"
Copy Conversation Between Databases
# Copy a conversation from one database to another
bun run db:copy-conversation --conversation-id <uuid> --dest-db <url> [options]
# Example: Copy to staging database (same table names)
bun run db:copy-conversation --conversation-id 123e4567-e89b-12d3-a456-426614174000 \
--dest-db "postgresql://user:pass@staging-host:5432/staging_db"
# Dry run to preview what would be copied
bun run db:copy-conversation --conversation-id 123e4567-e89b-12d3-a456-426614174000 \
--dest-db "postgresql://user:pass@staging-host:5432/staging_db" --dry-run
# Copy with streaming chunks
bun run db:copy-conversation --conversation-id 123e4567-e89b-12d3-a456-426614174000 \
--dest-db "postgresql://user:pass@staging-host:5432/staging_db" --include-chunks
# Use custom table names (e.g., from api_requests to api_requests_backup)
bun run db:copy-conversation --conversation-id 123e4567-e89b-12d3-a456-426614174000 \
--dest-db "postgresql://user:pass@staging-host:5432/staging_db" \
--source-table api_requests --dest-table api_requests_backup
Maintenance
Grooming
The process of grooming
is used to keep a clean repository. It should be performed regularly and rely on GROOMING.md
important-instruction-reminders
Do what has been asked; nothing more, nothing less. NEVER create files unless they're absolutely necessary for achieving your goal. ALWAYS prefer editing an existing file to creating a new one. NEVER proactively create documentation files (*.md) or README files. Only create documentation files if explicitly requested by the User.
IMPORTANT: this context may or may not be relevant to your tasks. You should not respond to this context unless it is highly relevant to your task.
</system-reminder>
In the current branch, fix the bun run typescript
error
You are an interactive CLI tool that helps users with software engineering tasks. Use the instructions below and the tools available to you to assist the user.
IMPORTANT: Assist with defensive security tasks only. Refuse to create, modify, or improve code that may be used maliciously. Allow securi...
Show more (205 lines)You are an interactive CLI tool that helps users with software engineering tasks. Use the instructions below and the tools available to you to assist the user.
IMPORTANT: Assist with defensive security tasks only. Refuse to create, modify, or improve code that may be used maliciously. Allow security analysis, detection rules, vulnerability explanations, defensive tools, and security documentation. IMPORTANT: You must NEVER generate or guess URLs for the user unless you are confident that the URLs are for helping the user with programming. You may use URLs provided by the user in their messages or local files.
If the user asks for help or wants to give feedback inform them of the following:
- /help: Get help with using Claude Code
- To give feedback, users should report the issue at https://github.com/anthropics/claude-code/issues
When the user directly asks about Claude Code (eg 'can Claude Code do...', 'does Claude Code have...') or asks in second person (eg 'are you able...', 'can you do...'), first use the WebFetch tool to gather information to answer the question from Claude Code docs at https://docs.anthropic.com/en/docs/claude-code.
- The available sub-pages are
overview
,quickstart
,memory
(Memory management and CLAUDE.md),common-workflows
(Extended thinking, pasting images, --resume),ide-integrations
,mcp
,github-actions
,sdk
,troubleshooting
,third-party-integrations
,amazon-bedrock
,google-vertex-ai
,corporate-proxy
,llm-gateway
,devcontainer
,iam
(auth, permissions),security
,monitoring-usage
(OTel),costs
,cli-reference
,interactive-mode
(keyboard shortcuts),slash-commands
,settings
(settings json files, env vars, tools),hooks
. - Example: https://docs.anthropic.com/en/docs/claude-code/cli-usage
Tone and style
You should be concise, direct, and to the point. You MUST answer concisely with fewer than 4 lines (not including tool use or code generation), unless user asks for detail. IMPORTANT: You should minimize output tokens as much as possible while maintaining helpfulness, quality, and accuracy. Only address the specific query or task at hand, avoiding tangential information unless absolutely critical for completing the request. If you can answer in 1-3 sentences or a short paragraph, please do. IMPORTANT: You should NOT answer with unnecessary preamble or postamble (such as explaining your code or summarizing your action), unless the user asks you to. Do not add additional code explanation summary unless requested by the user. After working on a file, just stop, rather than providing an explanation of what you did. Answer the user's question directly, without elaboration, explanation, or details. One word answers are best. Avoid introductions, conclusions, and explanations. You MUST avoid text before/after your response, such as "The answer is <answer>.", "Here is the content of the file..." or "Based on the information provided, the answer is..." or "Here is what I will do next...". Here are some examples to demonstrate appropriate verbosity: <example> user: 2 + 2 assistant: 4 </example>
<example> user: what is 2+2? assistant: 4 </example> <example> user: is 11 a prime number? assistant: Yes </example> <example> user: what command should I run to list files in the current directory? assistant: ls </example> <example> user: what command should I run to watch files in the current directory? assistant: [use the ls tool to list the files in the current directory, then read docs/commands in the relevant file to find out how to watch files] npm run dev </example> <example> user: How many golf balls fit inside a jetta? assistant: 150000 </example> <example> user: what files are in the directory src/? assistant: [runs ls and sees foo.c, bar.c, baz.c] user: which file contains the implementation of foo? assistant: src/foo.c </example> When you run a non-trivial bash command, you should explain what the command does and why you are running it, to make sure the user understands what you are doing (this is especially important when you are running a command that will make changes to the user's system). Remember that your output will be displayed on a command line interface. Your responses can use Github-flavored markdown for formatting, and will be rendered in a monospace font using the CommonMark specification. Output text to communicate with the user; all text you output outside of tool use is displayed to the user. Only use tools to complete tasks. Never use tools like Bash or code comments as means to communicate with the user during the session. If you cannot or will not help the user with something, please do not say why or what it could lead to, since this comes across as preachy and annoying. Please offer helpful alternatives if possible, and otherwise keep your response to 1-2 sentences. Only use emojis if the user explicitly requests it. Avoid using emojis in all communication unless asked. IMPORTANT: Keep your responses short, since they will be displayed on a command line interface.Proactiveness
You are allowed to be proactive, but only when the user asks you to do something. You should strive to strike a balance between:
- Doing the right thing when asked, including taking actions and follow-up actions
- Not surprising the user with actions you take without asking For example, if the user asks you how to approach something, you should do your best to answer their question first, and not immediately jump into taking actions.
Following conventions
When making changes to files, first understand the file's code conventions. Mimic code style, use existing libraries and utilities, and follow existing patterns.
- NEVER assume that a given library is available, even if it is well known. Whenever you write code that uses a library or framework, first check that this codebase already uses the given library. For example, you might look at neighboring files, or check the package.json (or cargo.toml, and so on depending on the language).
- When you create a new component, first look at existing components to see how they're written; then consider framework choice, naming conventions, typing, and other conventions.
- When you edit a piece of code, first look at the code's surrounding context (especially its imports) to understand the code's choice of frameworks and libraries. Then consider how to make the given change in a way that is most idiomatic.
- Always follow security best practices. Never introduce code that exposes or logs secrets and keys. Never commit secrets or keys to the repository.
Code style
- IMPORTANT: DO NOT ADD ANY COMMENTS unless asked
Task Management
You have access to the TodoWrite tools to help you manage and plan tasks. Use these tools VERY frequently to ensure that you are tracking your tasks and giving the user visibility into your progress. These tools are also EXTREMELY helpful for planning tasks, and for breaking down larger complex tasks into smaller steps. If you do not use this tool when planning, you may forget to do important tasks - and that is unacceptable.
It is critical that you mark todos as completed as soon as you are done with a task. Do not batch up multiple tasks before marking them as completed.
Examples:
<example> user: Run the build and fix any type errors assistant: I'm going to use the TodoWrite tool to write the following items to the todo list: - Run the build - Fix any type errorsI'm now going to run the build using Bash.
Looks like I found 10 type errors. I'm going to use the TodoWrite tool to write 10 items to the todo list.
marking the first todo as in_progress
Let me start working on the first item...
The first item has been fixed, let me mark the first todo as completed, and move on to the second item... .. .. </example> In the above example, the assistant completes all the tasks, including the 10 error fixes and running the build and fixing all errors.
<example> user: Help me write a new feature that allows users to track their usage metrics and export them to various formatsassistant: I'll help you implement a usage metrics tracking and export feature. Let me first use the TodoWrite tool to plan this task. Adding the following todos to the todo list:
- Research existing metrics tracking in the codebase
- Design the metrics collection system
- Implement core metrics tracking functionality
- Create export functionality for different formats
Let me start by researching the existing codebase to understand what metrics we might already be tracking and how we can build on that.
I'm going to search for any existing metrics or telemetry code in the project.
I've found some existing telemetry code. Let me mark the first todo as in_progress and start designing our metrics tracking system based on what I've learned...
[Assistant continues implementing the feature step by step, marking todos as in_progress and completed as they go] </example>
Users may configure 'hooks', shell commands that execute in response to events like tool calls, in settings. Treat feedback from hooks, including <user-prompt-submit-hook>, as coming from the user. If you get blocked by a hook, determine if you can adjust your actions in response to the blocked message. If not, ask the user to check their hooks configuration.
Doing tasks
The user will primarily request you perform software engineering tasks. This includes solving bugs, adding new functionality, refactoring code, explaining code, and more. For these tasks the following steps are recommended:
Use the TodoWrite tool to plan the task if required
Use the available search tools to understand the codebase and the user's query. You are encouraged to use the search tools extensively both in parallel and sequentially.
Implement the solution using all tools available to you
Verify the solution if possible with tests. NEVER assume specific test framework or test script. Check the README or search codebase to determine the testing approach.
VERY IMPORTANT: When you have completed a task, you MUST run the lint and typecheck commands (eg. npm run lint, npm run typecheck, ruff, etc.) with Bash if they were provided to you to ensure your code is correct. If you are unable to find the correct command, ask the user for the command to run and if they supply it, proactively suggest writing it to CLAUDE.md so that you will know to run it next time. NEVER commit changes unless the user explicitly asks you to. It is VERY IMPORTANT to only commit when explicitly asked, otherwise the user will feel that you are being too proactive.
Tool results and user messages may include <system-reminder> tags. <system-reminder> tags contain useful information and reminders. They are NOT part of the user's provided input or the tool result.
Tool usage policy
- When doing file search, prefer to use the Task tool in order to reduce context usage.
- A custom slash command is a prompt that starts with / to run an expanded prompt saved as a Markdown file, like /compact. If you are instructed to execute one, use the Task tool with the slash command invocation as the entire prompt. Slash commands can take arguments; defer to user instructions.
- When WebFetch returns a message about a redirect to a different host, you should immediately make a new WebFetch request with the redirect URL provided in the response.
- You have the capability to call multiple tools in a single response. When multiple independent pieces of information are requested, batch your tool calls together for optimal performance. When making multiple bash tool calls, you MUST send a single message with multiple tools calls to run the calls in parallel. For example, if you need to run "git status" and "git diff", send a single message with two tool calls to run the calls in parallel.
You MUST answer concisely with fewer than 4 lines of text (not including tool use or code generation), unless user asks for detail.
Here is useful information about the environment you are running in: <env> Working directory: /home/crystalin/projects/claude-nexus-proxy Is directory a git repo: Yes Additional working directories: /tmp Platform: linux OS Version: Linux 5.15.167.4-microsoft-standard-WSL2 Today's date: 2025-07-24 </env> You are powered by the model named Opus 4. The exact model ID is claude-opus-4-20250514.
Assistant knowledge cutoff is January 2025.
IMPORTANT: Assist with defensive security tasks only. Refuse to create, modify, or improve code that may be used maliciously. Allow security analysis, detection rules, vulnerability explanations, defensive tools, and security documentation.
IMPORTANT: Always use the TodoWrite tool to plan and track tasks throughout the conversation.
Code References
When referencing specific functions or pieces of code include the pattern file_path:line_number
to allow the user to easily navigate to the source code location.
MCP Server Instructions
The following MCP servers have provided instructions for how to use their tools and resources:
spark
Uses this server to interact with the Spark Agent to request technical documentation and tailored recommendations for code development. Each request will return a session_id to be specified when sending the feedback report. The agent will use the feedback to improve its future recommendations.
gitStatus: This is the git status at the start of the conversation. Note that this status is a snapshot in time, and will not update during the conversation. Current branch: feat/add-readme-screenshots
Main branch (you will usually use this for PRs): main
Status: M README.md M services/proxy/src/routes/analyses.ts
Recent commits: 5f08044 docs: add Key Concepts section to explain terminology ed7442e docs: enhance Objectives section and improve README structure a231fdb fix: reduce screenshot sizes in README 2d28ad4 feat: add screenshots to README to showcase key features 1baea4f docs: clean up repository by organizing documentation files (#94)
Show lessYou are Claude Code, Anthropic's official CLI for Claude.