Files
CherryHQ-cherry-studio/scripts/buildProxyBootstrapPlugin.ts
beyondkmp ea828787db fix(agents): route Claude Code child process through configured proxies (#13895)
### What this PR does

fix #13833

Before this PR:

Spawned Claude Code child processes did not reliably use the app's
configured proxy settings. HTTP proxy mode could work, but SOCKS proxy
mode could hang before the SDK returned any initial stream events.

After this PR:

Spawned Claude Code child processes inherit a dedicated Node-only proxy
bootstrap that applies the app's configured proxy settings for
fetch/undici, http/https, and axios. SOCKS proxy mode now avoids
exporting incompatible HTTP proxy env vars and emits clearer diagnostics
when proxy injection is active.

Fixes # None

### Why we need it and why it was done in this way

Claude Code runs as a standalone spawned `cli.js` process, so
main-process proxy patching and Electron session proxy configuration do
not automatically apply to it. This change builds a separate proxy
bootstrap, injects it only when proxy settings are configured, and keeps
the child-process proxy behavior aligned with the app's proxy settings
without changing the Claude SDK package itself.

The following tradeoffs were made:

- Added a separate build artifact for the Claude Code child-process
proxy bootstrap.
- Added child-process-specific proxy diagnostics to improve debugging
when proxy routing fails.
- Split SOCKS proxy environment handling from HTTP proxy handling to
avoid incompatible env combinations.

The following alternatives were considered:

- Relying only on inherited shell proxy environment variables.
- Relying on Electron session proxy configuration from the main process.
- Patching the Claude SDK package directly instead of injecting a local
bootstrap.

Links to places where the discussion took place: None

### Breaking changes

None.

If this PR introduces breaking changes, please describe the changes and
the impact on users.

No breaking changes.

### Special notes for your reviewer

- `out/proxy/index.js` is built as a standalone child-process bootstrap
and unpacked for packaged app usage.
- SOCKS proxy mode now exports `ALL_PROXY` / `SOCKS_PROXY` for the
Claude child process instead of forcing `HTTP_PROXY` / `HTTPS_PROXY` to
a SOCKS URL.
- Added tests covering HTTP vs SOCKS child-process proxy environment
generation.

### Checklist

This checklist is not enforcing, but it's a reminder of items that could
be relevant to every PR.
Approvers are expected to review this list.

- [x] PR: The PR description is expressive enough and will help future
contributors
- [x] Code: [Write code that humans can
understand](https://en.wikiquote.org/wiki/Martin_Fowler#code-for-humans)
and [Keep it simple](https://en.wikipedia.org/wiki/KISS_principle)
- [x] Refactor: You have [left the code cleaner than you found it (Boy
Scout
Rule)](https://learning.oreilly.com/library/view/97-things-every/9780596809515/ch08.html)
- [x] Upgrade: Impact of this change on upgrade flows was considered and
addressed if required
- [ ] Documentation: A [user-guide update](https://docs.cherry-ai.com)
was considered and is present (link) or not required. Check this only
when the PR introduces or changes a user-facing feature or behavior.
- [x] Self-review: I have reviewed my own code (e.g., via
[`/gh-pr-review`](/.claude/skills/gh-pr-review/SKILL.md), `gh pr diff`,
or GitHub UI) before requesting review from others

### Release note

<!--  Write your release note:
1. Enter your extended release note in the below block. If the PR
requires additional action from users switching to the new release,
include the string "action required".
2. If no release note is required, just write "NONE".
3. Only include user-facing changes (new features, bug fixes visible to
users, UI changes, behavior changes). For CI, maintenance, internal
refactoring, build tooling, or other non-user-facing work, write "NONE".
-->

```release-note
Fixed Claude Code agent sessions so spawned child processes respect configured HTTP and SOCKS proxy settings.
```

---------

Signed-off-by: beyondkmp <beyondkmp@gmail.com>
Signed-off-by: Payne Fu <payne@Paynes-MacBook-Air.local>
Co-authored-by: Payne Fu <payne@Paynes-MacBook-Air.local>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: suyao <sy20010504@gmail.com>
Co-authored-by: 亢奋猫 <kangfenmao@qq.com>
Co-authored-by: fullex <106392080+0xfullex@users.noreply.github.com>
2026-04-03 14:18:02 +08:00

53 lines
1.3 KiB
TypeScript

import { builtinModules } from 'node:module'
import { resolve } from 'path'
import { build as viteBuild, type Plugin } from 'vite'
interface BuildProxyBootstrapPluginOptions {
dependencies: string[]
isProd: boolean
rootDir: string
}
export const buildProxyBootstrapPlugin = ({
dependencies,
isProd,
rootDir
}: BuildProxyBootstrapPluginOptions): Plugin => {
return {
name: 'cherry-build-proxy-bootstrap',
apply: 'build',
async closeBundle() {
await viteBuild({
configFile: false,
publicDir: false,
resolve: {
mainFields: ['module', 'jsnext:main', 'jsnext'],
conditions: ['node']
},
build: {
outDir: resolve(rootDir, 'out/proxy'),
target: 'node22',
minify: false,
reportCompressedSize: false,
copyPublicDir: false,
lib: {
entry: resolve(rootDir, 'src/main/services/proxy/bootstrap.ts'),
formats: ['cjs'],
fileName: () => 'index.js'
},
rollupOptions: {
external: [
'electron',
/^electron\/.+/,
...builtinModules.flatMap((moduleName) => [moduleName, `node:${moduleName}`]),
...dependencies
]
}
},
esbuild: isProd ? { legalComments: 'none' } : {}
})
}
}
}