Compare commits

...
Author SHA1 Message Date
Peter Steinberger 53214abd08 fix: finalize proxy env support + changelog credits (#363) (thanks @kerrypotter) 2026-02-25 12:13:25 +00:00
Jarvis 4a6f4391c4 fix: use EnvHttpProxyAgent for proper proxy support
Address review feedback:
- Use undici's EnvHttpProxyAgent instead of ProxyAgent. This properly
  handles HTTPS_PROXY vs HTTP_PROXY per-scheme, respects NO_PROXY,
  and uses connect.timeout instead of requestTls.
- Update docs to mention NO_PROXY support.
2026-02-25 12:11:43 +00:00
Jarvis aa0a97bd35 fix: respect HTTP_PROXY/HTTPS_PROXY environment variables
The CLI creates a custom undici Agent via setGlobalDispatcher() which
overrides any proxy configuration. Since Node.js native fetch (backed
by undici) does not automatically respect HTTP_PROXY/HTTPS_PROXY env
vars, the CLI fails with 'fetch failed' on systems that require a
proxy for outbound connections.

Import ProxyAgent from undici and use it when any of the standard proxy
environment variables (HTTPS_PROXY, HTTP_PROXY, https_proxy, http_proxy)
is set. When no proxy variable is present, behavior is unchanged.

Also adds proxy documentation to cli.md and a troubleshooting entry.
2026-02-25 12:11:18 +00:00
6 changed files with 91 additions and 6 deletions
+2
View File
@@ -39,6 +39,8 @@
- Skills/Web: prevent filtered pagination dead-ends and loading-state flicker on `/skills`; move highlighted browse filtering into server list query (#339) (thanks @Marvae).
- Web: align `/skills` total count with public visibility and format header count (thanks @rknoche6, #76).
- Skills/Web: centralize public visibility checks and keep `globalStats` skill counts in sync incrementally; remove duplicate `/skills` default-sort fallback and share browse test mocks (thanks @rknoche6, #76).
- Moderation: clear stale `flagged.suspicious` flags when VirusTotal rescans improve to clean verdicts (#418) (thanks @Phineas1500).
- CLI: respect `HTTPS_PROXY`/`HTTP_PROXY`/`NO_PROXY` env vars for outbound registry requests, with troubleshooting docs (#363) (thanks @kerrypotter).
## 0.6.1 - 2026-02-13
-1
View File
@@ -2646,7 +2646,6 @@ export const approveSkillByHashInternal = internalMutation({
const existingFlags: string[] = (skill.moderationFlags as string[] | undefined) ?? []
const existingReason: string | undefined = skill.moderationReason as string | undefined
const alreadyBlocked = existingFlags.includes('blocked.malware')
const alreadyFlagged = existingFlags.includes('flagged.suspicious')
const bypassSuspicious =
isSuspicious && !alreadyBlocked && isPrivilegedOwnerForSuspiciousBypass(owner)
+28
View File
@@ -29,6 +29,34 @@ Env equivalents:
- `CLAWHUB_REGISTRY` (legacy `CLAWDHUB_REGISTRY`)
- `CLAWHUB_WORKDIR` (legacy `CLAWDHUB_WORKDIR`)
### HTTP proxy
The CLI respects standard HTTP proxy environment variables for systems behind
corporate proxies or restricted networks:
- `HTTPS_PROXY` / `https_proxy`
- `HTTP_PROXY` / `http_proxy`
- `NO_PROXY` / `no_proxy`
When any of these variables is set, the CLI routes outbound requests through
the specified proxy. `HTTPS_PROXY` is used for HTTPS requests, `HTTP_PROXY`
for plain HTTP. `NO_PROXY` / `no_proxy` is respected to bypass the proxy for
specific hosts or domains.
This is required on systems where direct outbound connections are blocked
(e.g. Docker containers, Hetzner VPS with proxy-only internet, corporate
firewalls).
Example:
```bash
export HTTPS_PROXY=http://proxy.example.com:3128
export NO_PROXY=localhost,127.0.0.1
clawhub search "my query"
```
When no proxy variable is set, behavior is unchanged (direct connections).
## Config file
Stores your API token + cached registry URL.
+19
View File
@@ -28,6 +28,25 @@ read_when:
- If many users share one egress IP (NAT/proxy), IP limit can be hit even with valid tokens.
- For non-Cloudflare deploys behind trusted proxies, set `TRUST_FORWARDED_IPS=true` so forwarded client IPs can be used.
## `search` / `install` fails with `fetch failed` behind a proxy
If your system requires an HTTP proxy for outbound connections (e.g. corporate
firewalls, Docker containers with proxy-only internet, Hetzner VPS), the CLI
will fail with:
```
✖ fetch failed
Error: fetch failed
```
**Fix:** Set the standard proxy environment variables:
```bash
export HTTPS_PROXY=http://proxy.example.com:3128
clawhub search "my query"
```
The CLI respects `HTTPS_PROXY`, `HTTP_PROXY`, `https_proxy`, and `http_proxy`.
## `publish` fails with `OPENAI_API_KEY is not configured`
- Set `OPENAI_API_KEY` in the Convex environment (not only locally).
+30 -1
View File
@@ -1,7 +1,7 @@
/* @vitest-environment node */
import { describe, expect, it, vi } from 'vitest'
import { apiRequest, apiRequestForm, downloadZip, fetchText } from './http'
import { apiRequest, apiRequestForm, downloadZip, fetchText, shouldUseProxyFromEnv } from './http'
import { ApiV1WhoamiResponseSchema } from './schema/index.js'
function mockImmediateTimeouts() {
@@ -36,6 +36,35 @@ function createAbortingFetchMock() {
})
}
describe('shouldUseProxyFromEnv', () => {
it('detects standard proxy variables', () => {
expect(
shouldUseProxyFromEnv({
HTTPS_PROXY: 'http://proxy.example:3128',
} as NodeJS.ProcessEnv),
).toBe(true)
expect(
shouldUseProxyFromEnv({
HTTP_PROXY: 'http://proxy.example:3128',
} as NodeJS.ProcessEnv),
).toBe(true)
expect(
shouldUseProxyFromEnv({
https_proxy: 'http://proxy.example:3128',
} as NodeJS.ProcessEnv),
).toBe(true)
})
it('ignores NO_PROXY-only configs', () => {
expect(
shouldUseProxyFromEnv({
NO_PROXY: 'localhost,127.0.0.1',
} as NodeJS.ProcessEnv),
).toBe(false)
expect(shouldUseProxyFromEnv({} as NodeJS.ProcessEnv)).toBe(false)
})
})
describe('apiRequest', () => {
it('adds bearer token and parses json', async () => {
const fetchMock = vi.fn().mockResolvedValue({
+12 -4
View File
@@ -3,7 +3,7 @@ import { mkdtemp, rm, writeFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import pRetry, { AbortError } from 'p-retry'
import { Agent, setGlobalDispatcher } from 'undici'
import { Agent, EnvHttpProxyAgent, setGlobalDispatcher } from 'undici'
import type { ArkValidator } from './schema/index.js'
import { ApiRoutes, parseArk } from './schema/index.js'
@@ -28,12 +28,20 @@ const CURL_WRITE_OUT_FORMAT = [
].join('\n')
const isBun = typeof process !== 'undefined' && Boolean(process.versions?.bun)
export function shouldUseProxyFromEnv(env: NodeJS.ProcessEnv = process.env): boolean {
return Boolean(env.HTTPS_PROXY || env.HTTP_PROXY || env.https_proxy || env.http_proxy)
}
if (typeof process !== 'undefined' && process.versions?.node) {
try {
setGlobalDispatcher(
new Agent({
connect: { timeout: REQUEST_TIMEOUT_MS },
}),
shouldUseProxyFromEnv(process.env)
? new EnvHttpProxyAgent({
connect: { timeout: REQUEST_TIMEOUT_MS },
})
: new Agent({
connect: { timeout: REQUEST_TIMEOUT_MS },
}),
)
} catch {
// ignore dispatcher setup failures in non-node runtimes