Skip to content

bug: DevTools activity logger fetch interceptor strips headers from Request objects #23481

@dlezama

Description

@dlezama

What happened?

When the DevTools feature is enabled (general.devtools: true in settings), the update check (and potentially other HTTP requests) fails because the ActivityLogger.patchGlobalFetch() interceptor strips headers from Request objects.

The patchGlobalFetch method in packages/cli/src/utils/activityLogger.ts replaces global.fetch with an interceptor. The interceptor reconstructs headers from init?.headers, but when the caller passes headers inside a Request object (the first argument) rather than in the init parameter, those headers are lost:

// activityLogger.ts, patchGlobalFetch()
global.fetch = async (input: RequestInfo | URL, init?: RequestInit) => {
  // ...
  const newInit = { ...init };
  const headers = new Headers(init?.headers || {});     // ← Bug: ignores input.headers
  headers.set(ACTIVITY_ID_HEADER, id);
  newInit.headers = headers;
  // ...
  const response = await originalFetch(input, newInit); // ← headers from Request object are overridden
};

The ky HTTP library (used by package-jsonlatest-versioncheckForUpdates) calls fetch(requestObject, {}), placing Authorization and Accept headers on the Request object. The interceptor creates new Headers({}), adds only the activity ID header, and passes that as newInit.headers — which overrides the Request object's headers when originalFetch is called.

This causes the update check to fail with a 403 when the npm registry requires authentication (e.g., private registries configured via .npmrc). It could also silently affect any other code path that uses globalThis.fetch with a Request object while devtools is enabled.

Reproduction steps

  1. Configure a private npm registry that requires authentication in ~/.npmrc
  2. Publish a package to that registry (or use any authenticated registry)
  3. Enable devtools: set "general": { "devtools": true } in .gemini/settings.json
  4. Run gemini interactively — the update check will fail with a 403

Alternatively, this can be observed by adding logging inside patchGlobalFetch:

// After: const headers = new Headers(init?.headers || {});
console.log('init?.headers:', init?.headers);  // undefined or {}
console.log('input.headers:', input instanceof Request ? Object.fromEntries(input.headers) : 'N/A');
// Shows that input.headers has Authorization, but headers does not

Root cause

The interceptor assumes headers are always in the init parameter. Per the Fetch API spec, headers can be on the Request object (first argument) instead. When init.headers is not provided, the interceptor should fall back to the Request object's headers.

Suggested fix

// Before:
const headers = new Headers(init?.headers || {});

// After:
const inputHeaders = typeof input === 'object' && 'headers' in input
  ? input.headers
  : undefined;
const headers = new Headers(init?.headers || inputHeaders || {});

Similarly, the method extraction should also consider the Request object:

// Before:
const method = (init?.method || 'GET').toUpperCase();

// After:
const inputMethod = typeof input === 'object' && 'method' in input
  ? input.method
  : undefined;
const method = (init?.method || inputMethod || 'GET').toUpperCase();

What did you expect to happen?

The DevTools activity logger should transparently intercept fetch calls without modifying request semantics. Headers from Request objects should be preserved and forwarded to the original fetch implementation.

Client information

  • Gemini CLI version: 0.33.2 (source tree; bug is in packages/cli/src/utils/activityLogger.ts)
  • Node.js: v24.13.0
  • Platform: Linux

Login information

Google Account

Anything else we need to know?

  • The bug only manifests when general.devtools is true in settings (which enables the activity logger and its fetch interceptor).
  • On the public npmjs.org registry, the stripped Authorization header doesn't cause a visible failure because the registry is public. However, users with private registries (configured via .npmrc with scoped auth tokens) will see 403 errors on the update check.
  • The Accept header is also silently stripped, which could cause subtle content negotiation issues.
  • The same bug likely affects the body extraction — init?.body is checked but input.body (from the Request object) is not.
  • Beyond the update check, any future code (or MCP server / extension) that calls globalThis.fetch with a Request object while devtools is enabled would be affected.

Metadata

Metadata

Assignees

Labels

area/coreIssues related to User Interface, OS Support, Core Functionalitytype/bug

Type

No fields configured for Bug.

Projects

Status

Closed

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions