Diagnose Claude launches using Orca proxy

This commit is contained in:
Brennan Benson
2026-08-27 11:56:26 -07:00
parent fecdf0bde8
commit c3e4281799
4 changed files with 63 additions and 2 deletions
@@ -0,0 +1,27 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { warnIfConfiguredClaudeProxy } from './claude-launch-proxy-notice'
const warning = vi.hoisted(() => vi.fn())
vi.mock('sonner', () => ({ toast: { warning } }))
describe('warnIfConfiguredClaudeProxy', () => {
beforeEach(() => warning.mockClear())
it('explains the configured proxy for Claude launches', () => {
warnIfConfiguredClaudeProxy('claude', { httpProxyUrl: 'http://127.0.0.1:9' })
expect(warning).toHaveBeenCalledWith(
expect.stringContaining(
'target host is routed through it unless covered by its bypass rules'
),
{
duration: 12_000
}
)
})
it('does not warn for agents without a configured proxy', () => {
warnIfConfiguredClaudeProxy('codex', { httpProxyUrl: 'http://127.0.0.1:9' })
warnIfConfiguredClaudeProxy('claude', {})
expect(warning).not.toHaveBeenCalled()
})
})
@@ -0,0 +1,18 @@
import { toast } from 'sonner'
import type { TuiAgent } from '../../../shared/tui-agent'
type ProxySettings = { httpProxyUrl?: string }
/** Explain the app-level proxy context before a Claude process makes its first request. */
export function warnIfConfiguredClaudeProxy(
agent: TuiAgent,
settings: ProxySettings | null | undefined
): void {
if (agent !== 'claude' || !settings?.httpProxyUrl?.trim()) {
return
}
toast.warning(
'Orca network proxy is configured for this Claude launch; the target host is routed through it unless covered by its bypass rules. If Claude reports ConnectionRefused, check Settings → Advanced → Network.',
{ duration: 12_000 }
)
}
@@ -13,6 +13,7 @@ const mockSeedNativeChatLaunchDraft = vi.fn()
const mockMarkNativeChatLaunchPromptFailed = vi.fn()
const mockTrack = vi.fn()
const mockToastMessage = vi.fn()
const mockToastWarning = vi.fn()
const LEAF_ID = '11111111-1111-4111-8111-111111111111'
@@ -93,9 +94,8 @@ vi.mock('@/store', () => ({
}))
const mockToastError = vi.fn()
vi.mock('sonner', () => ({
toast: { message: mockToastMessage, error: mockToastError }
toast: { message: mockToastMessage, error: mockToastError, warning: mockToastWarning }
}))
vi.mock('@/components/tab-bar/reconcile-order', () => ({
@@ -173,6 +173,16 @@ describe('launchAgentInNewTab', () => {
mockPasteDraftWhenAgentReady.mockResolvedValue(true)
})
it('does not attribute a local proxy to Claude launches on SSH hosts', async () => {
Reflect.set(store.settings, 'httpProxyUrl', 'http://127.0.0.1:9')
store.repos = [{ id: 'repo-1', connectionId: 'ssh-target-1', path: '/repo' }]
const { launchAgentInNewTab } = await import('./launch-agent-in-new-tab')
launchAgentInNewTab({ agent: 'claude', worktreeId: 'wt-1' })
expect(mockToastWarning).not.toHaveBeenCalled()
})
it('stamps the launched agent on the new tab for immediate provider icon bootstrap', async () => {
const { launchAgentInNewTab } = await import('./launch-agent-in-new-tab')
@@ -29,6 +29,7 @@ import type { LaunchSource } from '../../../shared/telemetry-events'
import { getConnectionIdFromState } from '@/lib/connection-context'
import { resolveInitialNativeChatSessionOptions } from '@/components/native-chat/native-chat-launch-session-options'
import { seedNativeChatAppliedSessionOptions } from '@/components/native-chat/native-chat-session-option-cache'
import { warnIfConfiguredClaudeProxy } from '@/lib/claude-launch-proxy-notice'
export type LaunchAgentInNewTabArgs = {
agent: TuiAgent
@@ -173,6 +174,11 @@ export function launchAgentInNewTab(args: LaunchAgentInNewTabArgs): LaunchAgentI
}
}
// The app-level proxy is injected into local PTYs (including WSL), not SSH or paired web hosts.
if (!isRemote) {
warnIfConfiguredClaudeProxy(agent, store.settings)
}
// Why: queue startup BEFORE TerminalPane mounts — it snapshots pendingStartupByTabId in useState on first render.
// Why: followup path pastes an unsubmitted draft, so gate the initial chat view like a draft launch, not auto-submit.
const tab = store.createTab(worktreeId, groupId, undefined, {