[Bug] Chrome plugin omits PlaywrightDownload.path() from the agent API

Open 💬 0 comments Opened Aug 1, 2026 by kkkzbh

What version of the Codex App are you using (From “About Codex” dialog)?

Codex App 26.727.40816

Bundled Chrome plugin version: 26.727.40816

What subscription do you have?

ChatGPT Pro

What platform is your computer?

Linux 7.1.5-200.fc44.x86_64 x86_64 unknown

Fedora Linux 44, x86_64.

The issue appears platform-independent because it is caused by the bundled
Chrome plugin API manifest and generated agent-facing documentation.

What issue are you seeing?

The Chrome plugin returns a PlaywrightDownload object from:

waitForEvent(
  event: "download",
  options?: WaitForEventOptions
): Promise<PlaywrightDownload>;

However, the generated API reference exposed to the agent declares the returned
object as an empty interface:

interface PlaywrightDownload {
}

Consequently, the supported agent-facing contract provides no way to retrieve
the local path of a completed download.

The runtime object does contain a functional path() method:

Object.getOwnPropertyNames(Object.getPrototypeOf(download));
// ["constructor", "path"]

Calling the hidden method succeeds:

const path = await download.path({ timeoutMs: 10000 });
// Returns the local path of the completed download.

The underlying plugin manifest contains the method declaration but explicitly
excludes it from generated documentation:

{
  "PlaywrightDownload": {
    "path": {
      "declarations": [
        {
          "text": "path(options: { timeoutMs?: number }): Promise<null | string>; // Return the local path to the downloaded file, if available.",
          "references": []
        }
      ],
      "documented": false
    }
  }
}

The runtime also implements the playwright_download_path command and returns a
nullable string path. The implementation, transport command, and returned
runtime object support this capability while the public API contract hides it.

This can cause an agent to report incorrectly that Chrome provides no downloaded
file path and that the user must save the file manually, even though the download
has already completed.

Practical consequences include:

  1. Agents cannot deterministically process, hash, inspect, or upload files they

just downloaded.

  1. Agents may ask the user to repeat a download manually.
  2. Retries can create duplicate downloads.
  3. Agents may resort to broad filesystem searches, which are less reliable and

less desirable for privacy and security.

  1. Custom Chrome download directories make filesystem guessing unreliable.

PlaywrightLocator.downloadMedia() returning Promise<void> is consistent with
its current declaration. The missing capability is a supported way to retrieve
the path from the download event object.

What steps can reproduce the bug?

This reproduces without authentication or private website content.

  1. Start a minimal local HTTP server:
const http = require("node:http");

const server = http.createServer((request, response) => {
  if (request.url === "/probe.txt") {
    response.writeHead(200, {
      "Content-Type": "application/octet-stream",
      "Content-Disposition":
        "attachment; filename=codex-download-path-probe.txt",
      "Content-Length": "5",
    });
    response.end("probe");
    return;
  }

  response.writeHead(200, {
    "Content-Type": "text/html; charset=utf-8",
  });
  response.end(`
    <!doctype html>
    <title>Download path probe</title>
    <a id="download" href="/probe.txt" download>Download probe</a>
  `);
});

server.listen(43127, "127.0.0.1");
  1. Select the Chrome plugin in a Codex task and open the page:
const tab = await chrome.tabs.new();
await tab.goto("http://127.0.0.1:43127/");
  1. Inspect the generated Chrome API reference. It contains:
interface PlaywrightDownload {
}
  1. Trigger and wait for the download:
const link = tab.playwright.locator("#download");

if (await link.count() !== 1) {
  throw new Error("Expected exactly one download link");
}

const pendingDownload = tab.playwright.waitForEvent("download", {
  timeoutMs: 10000,
});

await link.click({});
const download = await pendingDownload;
  1. Inspect the returned object:
Object.getOwnPropertyNames(download);
// []

Object.getOwnPropertyNames(Object.getPrototypeOf(download));
// ["constructor", "path"]
  1. Call the undocumented method:
const downloadedPath = await download.path({ timeoutMs: 10000 });

typeof downloadedPath;
// "string"

downloadedPath.split(/[\\/]/).at(-1);
// "codex-download-path-probe.txt"

The download succeeds and the method works, although the supported API reference
declares no methods on PlaywrightDownload.

What is the expected behavior?

The generated agent-facing API should expose the implemented download path
method:

interface PlaywrightDownload {
  path(options?: {
    timeoutMs?: number;
  }): Promise<null | string>;
}

The declaration should match the runtime implementation, which accepts an
omitted options object.

The Chrome plugin documentation should include a supported download example:

const pendingDownload = tab.playwright.waitForEvent("download", {
  timeoutMs: 10000,
});

await downloadLink.click({});

const download = await pendingDownload;
const path = await download.path({ timeoutMs: 10000 });

A contract test should verify that:

  1. PlaywrightDownload.path() is present in the generated API reference.
  2. waitForEvent("download") returns an object exposing that documented method.
  3. The method returns either a completed local path or null according to its

declared contract.

  1. The public declaration and runtime optional arguments remain aligned.

If exposing a local path is intentionally unsupported, the plugin should provide
another documented and deterministic way to retrieve the downloaded artifact.
The current state leaves a functional method accessible at runtime while
excluding it from the supported contract.

Additional information

Reproduction session ID:

019facb2-ac66-7b03-a79f-2a1546f13cb8

Token-limit and context-window usage are not relevant. The API mismatch also
reproduces in a fresh, short task using a five-byte local download.

The active docs/api.json, scripts/browser-client.mjs, and plugin manifest
match the staged bundled Chrome plugin payload. No local Linux patch modifies
these files or the PlaywrightDownload contract.

Related issue: #35051 requests a reliable downloaded-file path but reports a
different failure mode where the Chrome extension control request hangs and the
control host becomes unresponsive. In this reproduction, the download event
resolves, the file completes successfully, and the hidden path() method works.
The defect is the mismatch between the generated public API and the existing
runtime implementation.

No authenticated URL, cookies, account identifiers, downloaded private content,
or personal local paths are included in this report.

View original on GitHub ↗