Skip to content

v1.91.86.0 fix(browse): proxy from environment, Windows 8.3 temp paths, no NUL bunfig (Wave 9 Lane E) - #3135

Merged
capy-ai[bot] merged 7 commits into
mainfrom
wave9/e-browse
Oct 11, 2026
Merged

capy-ai[bot] merged 7 commits into
mainfrom
wave9/e-browse

Conversation

@garrytan

Copy link
Copy Markdown
Owner

Summary

Wave 9, lane E (browse). This lane covers four reported issues: #915 (proxy from the environment), #3104 (Windows 8.3 temp paths), #3085 (--config=NUL crash windows) and #3092 (did not reproduce, so it is closed separately).

#915: browse follows the proxy your environment sets

  • Precedence. --proxy comes first, then BROWSE_PROXY_URL, then HTTPS_PROXY/https_proxy, then HTTP_PROXY/http_proxy, then ALL_PROXY/all_proxy. With none of these set, the connection is direct. Setting BROWSE_PROXY_URL=direct opts out of the standard variables. All of this lives in resolveProxy (browse/src/proxy-config.ts).
  • Credential policy. Env URLs go through parseProxyConfig, so the same credential policy applies. The cred-mixing hint names the variable the URL came from.
  • Scheme handling. socks5h:// is accepted and mapped to Chromium's socks5://.
  • Unparseable variables. A standard variable that doesn't parse (curl-style host:port, an unsupported scheme, a bad port) prints a warning when the server starts, and browse connects directly. The warning never echoes the raw value, because it may carry credentials.
  • NO_PROXY. It is translated to Chromium bypass syntax. example.com and .example.com cover the domain plus its subdomains, * matches everything, CIDR blocks and host:port entries pass through, and bare IPv6 addresses get brackets.
  • Loopback. localhost, 127.0.0.1 and [::1] are always bypassed. Naming them also stops Playwright from adding <-loopback>, which would send localhost through the proxy; a negative control confirms this.
  • Every launch path. The resolved proxy, its source and the bypass rules reach every server launch: start, crash restart, headed connect and its supervisor. Chromium receives the bypass list on all four of its launch paths.
  • Startup line. One line names the proxy and its source, with credentials redacted, e.g. Starting server with proxy http://***:***@host:3128/ from HTTPS_PROXY....
  • Daemon config hash. The resolved proxy is part of the hash. So are the normalized NO_PROXY rules, but only when they contain non-loopback entries. Without NO_PROXY entries the hash is byte-identical to before, so running daemons are still reused across this upgrade.
  • Recorded state. The daemon's state file records the redacted proxy, its source and its bypass rules.
  • Mismatch refusal. When the daemon was started with different proxy settings and either side came from the environment, the refusal does four things:
    • names the variable that differs (NO_PROXY differs, HTTPS_PROXY differs)
    • prints the daemon's settings and the session's
    • offers browse stop, plus BROWSE_PROXY_URL=direct when the daemon has no proxy
    • links BROWSER.md#proxy-from-environment
      --proxy and headed differences keep their existing refusals.
  • Docs. BROWSER.md gets a new "Proxy from environment" section covering the precedence, both messages and their fixes.
  • Credit. Diagnosis and env order come from @ningfeemic-dev in fix(browse): honor proxy env vars in Playwright launches #916; this PR supersedes that implementation.

#3104: one realpath for safe directories and targets

  • Safe directories (SAFE_DIRECTORIES, TEMP_ONLY) and every target go through a single function, resolveRealPath, built on fs.realpathSync.native. It resolves the longest existing prefix and then appends the missing tail.
  • Resolved paths are compared exactly. Only a tail that doesn't exist yet is case-folded, and only on Windows.
  • validateOutputPath, validateReadPath and validateTempPath all use it.
  • cookie-import in browse/src/write-commands.ts had its own check built on fs.realpathSync. That check now calls validateReadPath, so it can't regress the same way.

#3085: no --config=NUL anywhere

  • browse/src/bun-child-config.ts adds emptyBunConfig().
    • On POSIX it returns /dev/null.
    • On Windows it creates bunfig.toml exclusively (wx, mode 0600) in a private mkdtemp directory. Before each spawn it checks that the file is still a zero-byte regular file (via lstat, so a symlink fails the check). If the file has grown or been replaced, a fresh one is created instead.
  • All three spawn sites use it: the terminal agent (bunChildFlags(), which replaces the BUN_CHILD_FLAGS constant), the native-cookie supervisor and its member worker.
  • The new file is listed in NATIVE_CODE_INPUTS.

#3092: does not reproduce on main

I ran the generated browse SKILL.md blocks from 1.91.83.0 with no aside on PATH and no project-local install. The output was NEEDS_ASIDE: Linux, then BROWSER_OK, then READY: ~/.claude/skills/gstack/browse/dist/browse. The queue closes the issue with that explanation.

Behavior changes

  • Proxy pickup. Users with HTTPS_PROXY, HTTP_PROXY or ALL_PROXY set now get Chromium routed through that proxy. Before, these variables were ignored. Opt out with BROWSE_PROXY_URL=direct.
  • Unparseable standard variables. A standard proxy variable that doesn't parse warns, and browse connects directly instead of failing. --proxy and BROWSE_PROXY_URL still fail closed when they don't parse, since they are explicit browse settings and a silent direct connection could expose the user's real IP.
  • NO_PROXY and loopback apply to every source, including --proxy. Before, Playwright sent localhost through a --proxy proxy.
  • Daemon reuse. A session whose proxy or NO_PROXY differs from the running daemon's is refused, where before it silently reused the daemon. This happens only when one side's proxy came from the environment.
  • browse stop always stops. It skips the config-mismatch check, so it stops a daemon started with different proxy or headed settings. Before, it was refused and told to use browse disconnect.
  • Read checks. validateReadPath now resolves the longest existing prefix of a missing path. For example, /tmp/new-dir/file on macOS is now accepted, because /tmp resolves to /private/tmp. Non-ENOENT failures on a parent now raise Cannot resolve real path instead of being ignored.
  • Windows Bun children get --config=<private empty bunfig.toml> instead of --config=NUL.
  • Exported symbol. BUN_CHILD_FLAGS is replaced by bunChildFlags().

Tests

New tests:

  • browse/test/proxy-env.test.ts: precedence, direct, socks5h, cred policy, the warning fallback with no echo, NO_PROXY translation, hash stability, server env, serverProxyEnv, mismatch lines, and the BROWSER.md anchor.
  • browse/test/proxy-env-daemon.test.ts: a local HTTP proxy driven through the real CLI and Chromium, covering launch, reuse with loopback kept direct, a NO_PROXY-only change being refused, BROWSE_PROXY_URL=direct being refused, restart after browse stop applying the new NO_PROXY, and the curl-style warning.
  • browse/test/path-security-realpath.test.ts: an aliased temp dir (an 8.3 short name on Windows, a symlink on POSIX), the case-folded missing tail on Windows only, and exact comparison.
  • browse/test/bun-child-config.test.ts: /dev/null on POSIX, the private zero-byte file, replacement after tampering, a child ignoring the project's bunfig preload, and a check that no spawn site passes NUL and every --config goes through the helper.

Results:

  • Focused: 187 pass, 0 fail across the 16 proxy/path files. The spawn-related files (dotenv-isolation, terminal-agent*, cookie-import-native*, windows-spawn-hide, browser-manager-unit, bridge-chromium-e2e) pass.
  • Gates: bun run typecheck and typecheck:test clean. Credential scan 0 high. Skill docs fresh. module-size-ratchet, spawnsync-timeout-tripwire and parity-suite pass.
  • Full free suite (bun run test:free on a Ubicloud standard-16 VM with zip installed), run on 32a9df6: 17 of 17 shards pass, 1186 files, 27,829 tests, 27,738 pass, 0 fail, 91 skip. The first full run, on 484353e, failed one case: browse/test/pair-agent-optin-gate.test.ts pins the exact source line let state = await ensureServer(globalFlags);, and the browse stop change had rewritten that line. 32a9df6 restores the line and applies the stop override on the line before it; after that, the focused test and the full rerun both passed.

Not verified

  • Windows runtime. The 8.3 alias test and the empty-bunfig spawn are exercised on Windows only by the windows-free-tests CI lane. I could not run Windows locally. The 8.3 case skips with a printed reason if the volume has short names disabled.
  • Bun 1.3.10 on Windows. I did not reproduce the original panic, since this machine runs Bun 1.4.2.
  • macOS. I did not run the live-proxy test on macOS, where Chromium's system proxy may also apply when browse is direct.
  • Real proxies. SOCKS5 with auth from an env variable goes through the existing bridge path, but I did not exercise it against a real authenticated upstream.

Credits

Supersedes #916. Contributed by @ningfeemic-dev: the diagnosis that every Chromium launch path ignored the standard proxy variables, and the HTTPS → HTTP → ALL order.

Open in Capy

garrytan and others added 5 commits October 11, 2026 15:22
…3085), proxy env (#915)

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…atchet

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
…t pins

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
@trunk-io

trunk-io Bot commented Oct 11, 2026 •

Copy link
Copy Markdown

😎 This pull request was merged.

@github-actions github-actions Bot changed the title v1.91.86.0 fix(browse): proxy from environment, Windows 8.3 temp paths, no NUL bunfig (Wave 9 Lane E) v1.91.85.0 fix(browse): proxy from environment, Windows 8.3 temp paths, no NUL bunfig (Wave 9 Lane E) Oct 11, 2026
garrytan and others added 2 commits October 11, 2026 15:45
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

# Conflicts:
#	CHANGELOG.md
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
@github-actions github-actions Bot changed the title v1.91.85.0 fix(browse): proxy from environment, Windows 8.3 temp paths, no NUL bunfig (Wave 9 Lane E) v1.91.86.0 fix(browse): proxy from environment, Windows 8.3 temp paths, no NUL bunfig (Wave 9 Lane E) Oct 11, 2026
garrytan added a commit that referenced this pull request Oct 11, 2026
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

E2E Evals: ✅ PASS

[test:paid] VERDICT GREEN — lane gate/pr, attempt 1
  rule 3/3 · behavior 0/0 · judge 2/2 · quarantined 0 (0 failing the lane)
  SKIPPED 0 · INFRA 0 · INCOMPLETE 0 · unattributed 0 · ACTION REQUIRED 0
  wall 0m47s · cost $0.47 known + 3 of 5 trial(s) cost unknown

4 executed, 0 reused rule/judge records | 0 manual accepted (unscored; no score-cache credit) | $.47 rule/judge cost | reconcile exit: 0

Profile: pr / mode pr; selected behaviors: 3, judges: 2. Deferred to scheduled/release coverage: 15 behaviors and 0 changed prompt files. Deferred checks did not run and receive no PR-pass credit.

Rule and judge shards
Shard Automated result Manual/unscored Executed Reused Status Cost
llm-judge/skill-llm-eval 2/2 0 2 0 ✅ $0.12
e2e-browse/skill-e2e-bws 2/2 0 2 0 ✅ $0.35
Fail-closed reconciliation
[test:paid] report: 1/1 slices, 3 planned shards, tier=gate, attempt 1
[test:paid] coverage: profile=pr mode=pr; selected E2E=3, judges=2
[test:paid] deferred: 15 broad behaviors, 0 changed prompts without quick live coverage; these are not PR passes
  slice 1  passed             21s  test/skill-llm-eval.test.ts
  slice 1  passed             33s  test/skill-e2e-bws.test.ts
  slice 1  passed             21s  test/skill-e2e-diagram.test.ts
[test:paid] collector final outcomes: 4 executed, 0 reused; 4 passed, 0 failed, 0 manual accepted (unscored; no score-cache credit) (4 attempt records from 2 collectors; every record counts)
[test:paid] deselected testcases, zero credit (27):
[test:paid]     26  outside the run's case selection
[test:paid]      1  periodic-tier case (this lane runs gate)
[test:paid] VERDICT GREEN — lane gate/pr, attempt 1
  rule 3/3 · behavior 0/0 · judge 2/2 · quarantined 0 (0 failing the lane)
  SKIPPED 0 · INFRA 0 · INCOMPLETE 0 · unattributed 0 · ACTION REQUIRED 0
  wall 0m47s · cost $0.47 known + 3 of 5 trial(s) cost unknown
[test:paid] report: every planned shard accounted and passed

Sliced lane: planner → duration-packed executors → fail-closed report. Behavior cases run a pre-registered 3-trial panel (PASS at 2/3 with no contract violation); a PASS 2/3 is shown with its failed trial, never as a clean pass. Reused results retain their original provenance and expiry.

@capy-ai
capy-ai Bot merged commit 47cd4a9 into main Oct 11, 2026
53 checks passed
@capy-ai
capy-ai Bot deleted the wave9/e-browse branch October 11, 2026 15:56
levi85 pushed a commit to levi85/gstack that referenced this pull request Oct 14, 2026
…s, no NUL bunfig (Wave 9 Lane E) (garrytan#3135)

browse resolves its proxy from --proxy, BROWSE_PROXY_URL, then HTTPS_PROXY,
HTTP_PROXY and ALL_PROXY (BROWSE_PROXY_URL=direct opts out), translates
NO_PROXY to Chromium bypass rules with loopback always kept, and hashes the
proxy and NO_PROXY rules into the daemon config so a differing environment is
refused with the variable named. Unparseable standard variables warn and go
direct; browse stop always stops (garrytan#915, supersedes garrytan#916).

Safe directories and targets resolve through one realpath
(fs.realpathSync.native), fixing Windows 8.3 temp paths (garrytan#3104). Bun children
get an empty private bunfig instead of --config=NUL, which panicked Bun
1.3.10 on Windows (garrytan#3085).

Co-authored-by: ningfeemic-dev <239259264+ningfeemic-dev@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant