garrytan/gstack · error · ProxyConfigError

proxy creds set in both env (BROWSE_PROXY_USER) and URL — pi

Error message

proxy creds set in both env (BROWSE_PROXY_USER) and URL — pick one source

What it means

Thrown by parseProxyConfig when credentials appear in BOTH the URL (user:pass@host) AND the environment (BROWSE_PROXY_USER / BROWSE_PROXY_PASS). This is the D9 'codex correction': the parser refuses to guess which source wins, because a silent override is a debugging trap — a stale env var from a prior session could win over a fresh --proxy URL and the user would have no idea why auth failed.

Source

Thrown at browse/src/proxy-config.ts:91

  if (!Number.isInteger(port) || port <= 0 || port > 65535) {
    throw new ProxyConfigError(
      'expected scheme://[user:pass@]host:port',
      `invalid proxy URL — bad port`,
    );
  }

  const urlHasUser = !!url.username;
  const urlHasPass = !!url.password;
  const envHasUser = !!opts.envUser;
  const envHasPass = !!opts.envPass;
  const urlHasCreds = urlHasUser || urlHasPass;
  const envHasCreds = envHasUser || envHasPass;

  // D9 (codex correction): refuse on mixed sources. Silent override is a
  // debugging trap — when a stale BROWSE_PROXY_USER from a prior session
  // wins over a fresh --proxy URL, the user can't tell why.
  if (urlHasCreds && envHasCreds) {
    throw new ProxyConfigError(
      'unset BROWSE_PROXY_USER/PASS or remove user:pass@ from --proxy',
      `proxy creds set in both env (BROWSE_PROXY_USER) and URL — pick one source`,
    );
  }

  let userId: string | undefined;
  let password: string | undefined;
  if (urlHasCreds) {
    userId = decodeURIComponent(url.username);
    password = url.password ? decodeURIComponent(url.password) : undefined;
  } else if (envHasCreds) {
    userId = opts.envUser;
    password = opts.envPass;
  }

  return {
    scheme: scheme as 'socks5' | 'http' | 'https',
    host: url.hostname,

View on GitHub (pinned to 94993f7401)

Solutions

  1. Unset the env vars: `unset BROWSE_PROXY_USER BROWSE_PROXY_PASS`
  2. OR remove user:pass@ from the --proxy URL and rely on env only
  3. Pick one credential source and document it in your shell rc / CI config
  4. Audit wrappers and dotfiles for stray BROWSE_PROXY_* exports

Example fix

# before: creds in both places
export BROWSE_PROXY_USER=alice
browse --proxy bob:pw@host:1080 ...  # throws

# after: pick one source
unset BROWSE_PROXY_USER
browse --proxy bob:pw@host:1080 ...
Defensive patterns

Strategy: validation

Validate before calling

function credsNotInBothPlaces(proxyUrl: string, envUser?: string, envPass?: string): boolean {
  let urlHasCreds = false;
  try {
    const u = new URL(proxyUrl);
    urlHasCreds = !!u.username || !!u.password;
  } catch { /* invalid URL handled elsewhere */ }
  const envHasCreds = !!envUser || !!envPass;
  return !(urlHasCreds && envHasCreds);
}

if (!credsNotInBothPlaces(proxyUrl, envUser, envPass)) {
  throw new Error('Set proxy creds in EITHER the URL OR the env, not both');
}

Type guard

function hasUrlCreds(s: string): boolean {
  try { const u = new URL(s); return !!u.username || !!u.password; } catch { return false; }
}

Try / catch

try {
  parseProxyConfig(opts);
} catch (e) {
  if (e instanceof ProxyConfigError && /both env/.test(e.message)) {
    console.error(`${e.message}. ${e.hint}`);
    // unset env and retry with URL creds
    delete process.env.BROWSE_PROXY_USER;
    delete process.env.BROWSE_PROXY_PASS;
    parseProxyConfig({ ...opts, envUser: undefined, envPass: undefined });
  } else throw e;
}

Prevention

When it happens

Trigger: Setting --proxy user:pass@host AND exporting BROWSE_PROXY_USER (or BROWSE_PROXY_PASS) in the same shell, then running the CLI. Both urlHasCreds and envHasCreds are true, so the parser throws.

Common situations: Stale env vars from a prior session combined with a fresh --proxy flag; CI that sets BROWSE_PROXY_USER globally and also embeds creds in the URL per-job; wrapper script that injects creds from both sources; switching auth strategies without clearing the old one.

Related errors


AI-assisted analysis of garrytan/gstack@94993f7401 (2026-08-12). Data as JSON: /api/errors/4ad0cb454476c049. Report an issue: GitHub.