Skip to content
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ Avoid type assertions except `as const`. Unavoidable casts need a line-specific
- **Test**: `pnpm test [path/to/file.test.ts]`
- **Lint**: `oxlint`, or `pnpm run check:code-quality:changed` for changed files (full `pnpm lint` is slow); format with `pnpm format`
- **Design system**: `pnpm run lint:design-system` for the full renderer report (not a gate); the changed-lines gate above is what CI enforces
- **Real Claude CLI**: when you change Claude structured-session code (`src/main/claude/claude-structured-*`), run `ORCA_REAL_CLAUDE_CLI_TEST=1 pnpm test src/main/claude/claude-structured-real-cli.test.ts src/main/claude/claude-structured-real-cli-fold.test.ts`; it uses your real Claude login

# Writing Pull Requests

Expand Down
29 changes: 12 additions & 17 deletions src/main/claude/claude-real-cli-availability-test-support.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
// Whether a real, signed-in Claude CLI is present — the gate every real-CLI
// suite skips on. Probed once per test process.
// Whether a real, signed-in Claude CLI may be used — the gate every real-CLI
// suite skips on. Opt-in only; probed once per test process.

import { homedir } from 'node:os'
import { join } from 'node:path'
import { runProcessSync, type ProcessResult } from '../../shared/child-process/run-process'
import { withCliRuntimeOnPath } from '../../shared/node-cli-command-resolution'
import { CLAUDE_AUTH_ENV_VARS } from '../claude-accounts/environment'
import { resolveClaudeCommand } from '../codex-cli/command'
import { resolveRealClaudeCliGate } from './claude-real-cli-test-gate'

export const realClaudeCommand = resolveClaudeCommand()

Expand All @@ -25,24 +26,18 @@ function probeRealClaude(args: string[]): ProcessResult | null {
}
}

export const realClaudeAvailable = probeRealClaude(['--version'])?.code === 0
// Why the gate: an installed, signed-in CLI is not consent to spend turns on that account, so
// nothing is probed unless the run opted in.
export const realClaudeCliGate = resolveRealClaudeCliGate(process.env, (args) => {
const result = probeRealClaude([...args])
return { status: result?.code ?? null, stdout: result?.stdout ?? '' }
})

export const realClaudeAvailable = realClaudeCliGate.skipReason === null

/** The CLI's own account report — the only source of truth for where it writes that
* is not derived from Orca's own path expressions. */
export const realClaudeAuthStatus = (() => {
if (!realClaudeAvailable) {
return null
}
const result = probeRealClaude(['auth', 'status', '--json'])
if (!result || result.code !== 0) {
return null
}
try {
return JSON.parse(result.stdout) as { loggedIn?: boolean; projectsDirectory?: string }
} catch {
return null
}
})()
export const realClaudeAuthStatus = realClaudeCliGate.authStatus

export const realClaudeAuthenticated = realClaudeAuthStatus?.loggedIn === true

Expand Down
85 changes: 85 additions & 0 deletions src/main/claude/claude-real-cli-test-gate.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { describe, expect, it, vi } from 'vitest'
import {
REAL_CLAUDE_CLI_TEST_ENV,
resolveRealClaudeCliGate,
type ClaudeCliProbeResult
} from './claude-real-cli-test-gate'

/** A claude that is installed and signed in, as far as the probes can tell. */
function signedInClaude() {
return vi.fn((args: readonly string[]): ClaudeCliProbeResult => {
if (args[0] === '--version') {
return { status: 0, stdout: '2.1.0 (Claude Code)\n' }
}
return {
status: 0,
stdout: JSON.stringify({ loggedIn: true, projectsDirectory: '/home/dev/.claude/projects' })
}
})
}

describe('resolveRealClaudeCliGate', () => {
it.each([undefined, '', '0', 'true'])(
'skips without probing a signed-in claude when %s is the opt-in value',
(value) => {
const runClaude = signedInClaude()

const gate = resolveRealClaudeCliGate({ [REAL_CLAUDE_CLI_TEST_ENV]: value }, runClaude)

expect(gate).toEqual({
skipReason: 'set ORCA_REAL_CLAUDE_CLI_TEST=1 to run against the real claude CLI',
authStatus: null
})
expect(runClaude).not.toHaveBeenCalled()
}
)

it('runs with the CLI account report once opted in and signed in', () => {
const runClaude = signedInClaude()

const gate = resolveRealClaudeCliGate({ [REAL_CLAUDE_CLI_TEST_ENV]: '1' }, runClaude)

expect(gate).toEqual({
skipReason: null,
authStatus: { loggedIn: true, projectsDirectory: '/home/dev/.claude/projects' }
})
expect(runClaude.mock.calls).toEqual([[['--version']], [['auth', 'status', '--json']]])
})

it('still skips when opted in but no claude binary answers', () => {
const runClaude = vi.fn((): ClaudeCliProbeResult => ({ status: null, stdout: '' }))

const gate = resolveRealClaudeCliGate({ [REAL_CLAUDE_CLI_TEST_ENV]: '1' }, runClaude)

expect(gate).toEqual({ skipReason: '`claude --version` failed', authStatus: null })
expect(runClaude).toHaveBeenCalledTimes(1)
})

it.each([
['a failed auth probe', { status: 1, stdout: '' }],
['unparseable auth output', { status: 0, stdout: 'not json' }]
])('runs with no account report after %s', (_label, authResult) => {
const runClaude = vi.fn((args: readonly string[]): ClaudeCliProbeResult =>
args[0] === '--version' ? { status: 0, stdout: '2.1.0\n' } : authResult
)

// The suite's signed-out case still runs; its signed-in cases skip on a null report.
expect(resolveRealClaudeCliGate({ [REAL_CLAUDE_CLI_TEST_ENV]: '1' }, runClaude)).toEqual({
skipReason: null,
authStatus: null
})
})

it('keeps only the account fields it understands', () => {
const runClaude = vi.fn((args: readonly string[]): ClaudeCliProbeResult =>
args[0] === '--version'
? { status: 0, stdout: '2.1.0\n' }
: { status: 0, stdout: JSON.stringify({ loggedIn: 'yes', projectsDirectory: 7 }) }
)

expect(resolveRealClaudeCliGate({ [REAL_CLAUDE_CLI_TEST_ENV]: '1' }, runClaude)).toEqual({
skipReason: null,
authStatus: {}
})
})
})
52 changes: 52 additions & 0 deletions src/main/claude/claude-real-cli-test-gate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/** Opt-in for tests that drive the real `claude` CLI on this machine's signed-in account. */
export const REAL_CLAUDE_CLI_TEST_ENV = 'ORCA_REAL_CLAUDE_CLI_TEST'

export type ClaudeCliProbeResult = { status: number | null; stdout: string }

export type RealClaudeAuthStatus = { loggedIn?: boolean; projectsDirectory?: string }

export type RealClaudeCliGate = {
/** Null when the real-CLI suite may run; otherwise why it is skipped. */
skipReason: string | null
/** The CLI's own account report; null when skipped or unreadable. */
authStatus: RealClaudeAuthStatus | null
}

function parseAuthStatus(stdout: string): RealClaudeAuthStatus | null {
let parsed: unknown
try {
parsed = JSON.parse(stdout)
} catch {
return null
}
if (!parsed || typeof parsed !== 'object') {
return null
}
const status: RealClaudeAuthStatus = {}
if ('loggedIn' in parsed && typeof parsed.loggedIn === 'boolean') {
status.loggedIn = parsed.loggedIn
}
if ('projectsDirectory' in parsed && typeof parsed.projectsDirectory === 'string') {
status.projectsDirectory = parsed.projectsDirectory
}
return status
}

export function resolveRealClaudeCliGate(
env: NodeJS.ProcessEnv,
runClaude: (args: readonly string[]) => ClaudeCliProbeResult
): RealClaudeCliGate {
// Why before any probe: an installed, signed-in CLI is not consent to spend turns on that
// account and write transcripts into its home from a broad local test run.
if (env[REAL_CLAUDE_CLI_TEST_ENV] !== '1') {
return {
skipReason: `set ${REAL_CLAUDE_CLI_TEST_ENV}=1 to run against the real claude CLI`,
authStatus: null
}
}
if (runClaude(['--version']).status !== 0) {
return { skipReason: '`claude --version` failed', authStatus: null }
}
const auth = runClaude(['auth', 'status', '--json'])
return { skipReason: null, authStatus: auth.status === 0 ? parseAuthStatus(auth.stdout) : null }
}
4 changes: 3 additions & 1 deletion src/main/claude/claude-structured-real-cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
realClaudeAuthenticated,
realClaudeAuthStatus,
realClaudeAvailable,
realClaudeCliGate,
realClaudeCommand
} from './claude-real-cli-availability-test-support'
import {
Expand All @@ -19,6 +20,7 @@ import {
import type { ClaudeStructuredSessionAdapterDeps } from './claude-structured-session-state'

const command = realClaudeCommand
const suiteTitle = `Claude structured real CLI handshake${realClaudeCliGate.skipReason ? ` (skipped: ${realClaudeCliGate.skipReason})` : ''}`

function realAdapter(
providerSessionId: string,
Expand Down Expand Up @@ -79,7 +81,7 @@ async function waitForResolvedTranscript(
}
}

describe.skipIf(!realClaudeAvailable)('Claude structured real CLI handshake', () => {
describe.skipIf(!realClaudeAvailable)(suiteTitle, () => {
it.skipIf(!realClaudeAuthenticated)(
'proves a pre-minted session before the first user message',
async () => {
Expand Down
Loading