Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions src/workspaces/adapters/claude.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { describe, it, expect } from 'vitest'
import { claudeAdapter } from './claude.js'

import type { SpawnContext } from '../cli-adapter.js'

// Every spawn carries this flag so project-scoped `.mcp.json` servers are
// auto-trusted instead of parking at "⏸ Pending approval". See adapter comment.
const SETTINGS = ['--settings', JSON.stringify({ enableAllProjectMcpServers: true })]

describe('claudeAdapter.composeCommand', () => {
const ctx = (resume: SpawnContext['resume']): SpawnContext => ({
cwd: '/tmp/ws',
env: {},
resume,
})

it('injects --settings (mcp auto-trust) for fresh (no resume)', () => {
expect(claudeAdapter.composeCommand(['claude'], ctx(undefined))).toEqual(['claude', ...SETTINGS])
})

it('injects --settings then --resume <id> for resume-by-id', () => {
expect(claudeAdapter.composeCommand(['claude'], ctx({ sessionId: 'abc-123' }))).toEqual([
'claude',
...SETTINGS,
'--resume',
'abc-123',
])
})

it('emits a parseable settings JSON enabling enableAllProjectMcpServers', () => {
const cmd = claudeAdapter.composeCommand(['claude'], ctx(undefined))
const i = cmd.indexOf('--settings')
expect(i).toBeGreaterThanOrEqual(0)
expect(JSON.parse(cmd[i + 1]!)).toEqual({ enableAllProjectMcpServers: true })
})

it('throws for resume="last"', () => {
// resume="last" is unsupported; see adapter comment.
expect(() => claudeAdapter.composeCommand(['claude'], ctx('last' as never))).toThrow()
})
})
21 changes: 19 additions & 2 deletions src/workspaces/adapters/claude.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,30 @@ export const claudeAdapter: CliAdapter = {
},

composeCommand(base: readonly string[], ctx: SpawnContext): readonly string[] {
if (ctx.resume === undefined) return base;
// Auto-trust the workspace's project-scoped `.mcp.json` servers. Without
// this, Claude Code parks every project MCP server at "⏸ Pending approval"
// (the trust gate for `.mcp.json` shared via VCS) and the agent never sees
// the OpenAlice tool surface — the symptom users hit as "MCP 啟動唔到".
//
// Injected on the command line (not via a written `.claude/settings.json`)
// to match the launcher's CLI-injection philosophy — same as the codex
// adapter's `-c mcp_servers.openalice.url=...`. Verified empirically that
// neither `--mcp-config`, `--dangerously-skip-permissions`, nor
// `--permission-mode bypassPermissions` clears this gate; only the
// `enableAllProjectMcpServers` setting does. `--settings` accepts an inline
// JSON string (PTY spawn passes argv directly, so no shell-quoting needed).
const cmd = [
...base,
'--settings',
JSON.stringify({ enableAllProjectMcpServers: true }),
];
if (ctx.resume === undefined) return cmd;
if (ctx.resume === 'last') {
throw new Error(
'claude adapter: "last" resume not supported — use --resume <sessionId> or undefined (fresh)',
);
}
return [...base, '--resume', ctx.resume.sessionId];
return [...cmd, '--resume', ctx.resume.sessionId];
},

transcriptDir(cwd: string): string {
Expand Down