Skip to content

fix(app,gui-plugins): port renderer UUID generation to HTTP-safe uuid() - #60462

Merged
Hona merged 4 commits into
devfrom
file-picker-uuid
Oct 30, 2026
Merged

Hona merged 4 commits into
devfrom
file-picker-uuid

Conversation

@Hona

@Hona Hona commented Oct 30, 2026 •

Copy link
Copy Markdown
Member

Fixes #59791
Supersedes #59860

  • Move uuid() to @opencode/util/uuid (packages/util/src/uuid.ts): Moves the isSecureContext and crypto.randomUUID compatibility helper and its unit tests out of @opencode/app into @opencode/util/uuid, updating @opencode/app callers directly so @opencode/gui-plugins and @opencode/app share one module with no re-export shim.
  • Port every remaining renderer crypto.randomUUID() call to uuid() (packages/gui-plugins, packages/app):
    • packages/gui-plugins/src/file/renderer.tsx: Side-panel file:open:<id> launcher tabs (+ → Open file and Ctrl+Alt+P) on wide viewports.
    • packages/gui-plugins/src/btw/model.ts: /btw side-panel quick chat IDs.
    • packages/gui-plugins/src/terminal/panel.tsx: Terminal selection Quote composer attachment IDs.
    • packages/app/src/runtime/plugin/host-apis.tsx: Window plugin host ctx.notify(...) deduplication keys.
    • packages/gui-plugins/src/browser/comment.ts, connection.ts, ssh/dialog.tsx, and packages/app/src/voice/transport.ts: Desktop-gated renderer IDs so zero raw crypto.randomUUID() calls remain in shipped window code.
  • Document insecure-origin rule (packages/app/AGENTS.md, packages/gui-plugins/AGENTS.md): Instructs contributors and agents never to call [SecureContext]-only crypto.randomUUID() directly in renderer/browser code and to use uuid() from @opencode/util/uuid.
  • Extend Review e2e keeper (packages/app/e2e/regression/review.spec.ts): Verifies multiple Open file launcher tabs open when isSecureContext is false and crypto.randomUUID is undefined.
flowchart LR
  A["Window / Web UI Callers<br/>file · btw · terminal · notify · composer · tabs"] --> B["uuid()<br/>@opencode/util/uuid"]
  B -->|"isSecureContext && crypto.randomUUID"| C["crypto.randomUUID()"]
  B -->|"HTTP LAN / Tailscale (isSecureContext = false)"| D["Math.random().toString(16).slice(2)"]
Loading
// packages/gui-plugins/src/file/renderer.tsx
import { uuid } from "@opencode/util/uuid"

if (selected || layout.narrow())
  layout.open(`file:${selected ?? OPEN}`, session, { tab: "preview", background: options?.background })
else layout.open(`file:${OPEN}:${uuid()}`, session, { tab: "select" })
Before After
Before: Open file click throws and stays on New tab After: Open file picker opens over insecure HTTP

Visual

<svg viewBox="0 0 240 72" width="240" height="72" fill="none" xmlns="http://www.w3.org/2000/svg">
  <style>
    .tab { animation: tab-in 3.2s ease-in-out infinite; }
    .rows { animation: rows-in 3.2s ease-in-out infinite; }
    @keyframes tab-in {
      0%, 15% { opacity: 0.45; transform: translateX(-4px); }
      32%, 82% { opacity: 1; transform: translateX(0); }
      100% { opacity: 0.45; transform: translateX(-4px); }
    }
    @keyframes rows-in {
      0%, 20% { opacity: 0.35; }
      38%, 82% { opacity: 1; }
      100% { opacity: 0.35; }
    }
  </style>
  <rect x="20" y="10" width="200" height="52" rx="4" stroke="currentColor" stroke-opacity="0.28" />
  <line x1="20" y1="26" x2="220" y2="26" stroke="currentColor" stroke-opacity="0.22" />
  <line x1="96" y1="26" x2="96" y2="62" stroke="currentColor" stroke-opacity="0.2" />
  <g class="tab">
    <rect x="26" y="13" width="58" height="10" rx="2" fill="currentColor" fill-opacity="0.12" stroke="currentColor" stroke-opacity="0.6" />
    <rect x="33" y="17" width="36" height="2" rx="1" fill="currentColor" fill-opacity="0.85" />
  </g>
  <path d="M92 18h6M95 15v6" stroke="currentColor" stroke-opacity="0.45" stroke-width="1.3" stroke-linecap="round" />
  <g class="rows">
    <rect x="27" y="32" width="62" height="8" rx="2" stroke="currentColor" stroke-opacity="0.5" />
    <rect x="31" y="35" width="28" height="2" rx="1" fill="currentColor" fill-opacity="0.45" />
    <rect x="30" y="45" width="38" height="2.5" rx="1" fill="currentColor" fill-opacity="0.75" />
    <rect x="30" y="52" width="46" height="2.5" rx="1" fill="currentColor" fill-opacity="0.5" />
    <path d="M148 39h8l2 3h12v10h-22V39z" stroke="currentColor" stroke-opacity="0.55" stroke-width="1.3" stroke-linejoin="round" />
    <rect x="138" y="55" width="42" height="2" rx="1" fill="currentColor" fill-opacity="0.4" />
  </g>
</svg>

Added by the opencode bot (AI-generated).

Fixes #55777

Related: #46168

Copilot AI balanced review requested due to automatic review settings October 30, 2026 04:17
@Hona
Hona requested a review from Brendonovich as a code owner October 30, 2026 04:17

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

@opencodejr opencodejr Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix works. Over plain HTTP from a LAN IP (isSecureContext false, crypto.randomUUID undefined), Open file throws crypto.randomUUID is not a function on the base and opens the picker tab with this change. I checked with the issue's reproduction script against servers run from source in both checkouts. The new e2e test passes here and fails on the base.

Public API: unchanged. @opencode/util/uuid is a new internal module, available through the existing ./* export.

One thing to fix: the moved packages/util/src/uuid.test.ts still has import { uuid } from "./uuid". packages/util uses nodenext resolution, so tsc --noEmit there now fails with TS2835 (the base passes). Changing the import to ./uuid.js, like the package's other tests do, fixes it.

Related: #46168, #55777

Before

After

@opencodejr opencodejr Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix still works: I rebuilt from this head and re-ran the issue's script over plain HTTP from a LAN IP. Open file opens the picker, and nothing is thrown. Pointing the app's callers straight at @opencode/util/uuid and removing the shim is clean, and tsc -b in packages/app passes. The public API is unchanged.

Still open from last time: packages/util/src/uuid.test.ts imports ./uuid, so tsc --noEmit in packages/util still fails with TS2835. It needs ./uuid.js.

The new typing change in createCommentSessionState (packages/app/src/composer/comments.tsx) only changes types and has nothing to do with this fix, so it can be dropped to keep the change focused.

Before

After

@opencodejr opencodejr Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed: the test now imports ./uuid.js, and tsc --noEmit in packages/util passes. bun test src/uuid.test.ts passes too. Nothing new to raise. The public API is unchanged.

@Hona Hona changed the title fix(gui-plugins): reuse HTTP-safe uuid helper for file picker tabs fix(app,gui-plugins): port renderer UUID generation to HTTP-safe uuid() Oct 30, 2026
@Hona
Hona merged commit fef2323 into dev Oct 30, 2026
22 checks passed
@Hona
Hona deleted the file-picker-uuid branch October 30, 2026 04:50

@opencodejr opencodejr Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new commits look good. Over plain HTTP from a LAN IP, /btw showed "Failed to send command … crypto.randomUUID is not a function" on the base. With this change there's no error and the side chat opens. Open file still opens the picker. tsc -b passes in packages/app and packages/gui-plugins, and oxlint finds nothing in the changed plugin files. The only direct randomUUID() calls left are in desktop main-process code. The public API is unchanged. Nothing new to raise.

Before

After

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants