fix(vercel): force HTTPS for typescript-client git dep on Vercel (#391)

npm normalizes the `github:OpenHands/typescript-client#sha` shorthand
(and even an explicit `git+https://github.com/...` URL) to
`git+ssh://git@github.com/...` whenever it rewrites package-lock.json
during a plain `npm install`. Vercel's build environment has no GitHub
SSH key, so an ssh-pinned lockfile causes Vercel to fall back to a stale
cached copy of the package whose dist/clients.js predates the addition
of ConversationClient, FileClient, and SharedClient. Rolldown then
fails the build with:

  [MISSING_EXPORT] ConversationClient is not exported by
  node_modules/@openhands/typescript-client/dist/clients.js

PR #382 fixed this once by hand-editing the lockfile, but the very next
local `npm install` (e.g. PR #387 bumping React Query hooks) silently
rewrote the resolved URL back to ssh and the bug returned.

This change makes the Vercel build self-healing:

* package.json now pins the dep as an explicit `git+https://` URL so
  the intent is documented in one place.
* package-lock.json's top-level dep spec matches that URL; the nested
  `node_modules/@openhands/typescript-client` entry already resolved
  to https, so this brings both halves of the lockfile in sync.
* vercel.json sets `installCommand` to `bash scripts/vercel-install.sh`,
  which:
    - rewrites any leftover `git+ssh://git@github.com/` resolved URLs
      back to https (handles future regressions),
    - configures `git config --global url."https://github.com/".insteadOf`
      for both `ssh://git@github.com/` and `git@github.com:` (handles
      anything npm has already normalized in cache),
    - then runs `npm ci` for a strict, lockfile-driven install.

Locally verified:

* `bash scripts/vercel-install.sh` produces a clean install with the
  https-resolved typescript-client.
* `npm run build` and `npm run lint` both succeed after the install.
* Re-running `npm install` rewrites `resolved` back to `git+ssh` as
  expected — the install script normalizes it again on every Vercel
  build, so the lockfile drift no longer breaks deploys.

Refs: #384 (Vercel preview build fails: MISSING_EXPORT for SharedClient
/ ConversationClient / FileClient).

Co-authored-by: openhands <openhands@all-hands.dev>
This commit is contained in:
Robert Brennan
2026-05-12 14:08:50 -07:00
committed by GitHub
parent 921ea743ef
commit 8b7ed9de2f
5 changed files with 40 additions and 2 deletions
+1
View File
@@ -54,6 +54,7 @@
- `__tests__/i18n/library-namespace.test.ts` imports the full library entry and can exceed Vitest's default 5s timeout under full-suite load; keep an explicit higher timeout on that case unless the test is substantially narrowed. - `__tests__/i18n/library-namespace.test.ts` imports the full library entry and can exceed Vitest's default 5s timeout under full-suite load; keep an explicit higher timeout on that case unless the test is substantially narrowed.
- `@openhands/typescript-client` is currently pinned to commit `ef62e82fc3dfb03991a1c8025429caf354427263` because the package metadata needed by this PR has not been published as a consistent npm/tagged release yet. That commit ships the needed typed clients plus subpath exports for `client/http-client`, `events/remote-events-list`, and `workspace/remote-workspace`. `RemoteWorkspace.gitChanges`/`gitDiff` accept an optional `{ ref }` option; agent-canvas passes `'HEAD'` so the changes panel reflects working-tree + index versus the latest commit (i.e. staged + unstaged) instead of a diff against the upstream/default branch. - `@openhands/typescript-client` is currently pinned to commit `ef62e82fc3dfb03991a1c8025429caf354427263` because the package metadata needed by this PR has not been published as a consistent npm/tagged release yet. That commit ships the needed typed clients plus subpath exports for `client/http-client`, `events/remote-events-list`, and `workspace/remote-workspace`. `RemoteWorkspace.gitChanges`/`gitDiff` accept an optional `{ ref }` option; agent-canvas passes `'HEAD'` so the changes panel reflects working-tree + index versus the latest commit (i.e. staged + unstaged) instead of a diff against the upstream/default branch.
- The `@openhands/typescript-client` git dep must be expressed as a `git+https://github.com/...` URL in both `package.json` and the top-level dep entry of `package-lock.json`; the `github:OpenHands/...` shorthand normalizes to `git+ssh://` inside the lockfile, and Vercel's build environment has no GitHub SSH key, so an ssh-pinned lockfile makes Vercel fall back to a stale cached tarball and the bundler then fails with `[MISSING_EXPORT] ConversationClient/FileClient/SharedClient is not exported by .../dist/clients.js`. `scripts/vercel-install.sh` (wired up via `vercel.json`'s `installCommand`) defensively rewrites any leftover `git+ssh://git@github.com/` resolved URLs to `git+https://github.com/` and adds matching `git config --global url..insteadOf` aliases before invoking `npm ci`, so a future regression that re-introduces an ssh-pinned lockfile entry still builds on Vercel. See GitHub issue #384 for the original failure and PR #382 for the prior single-shot lockfile fix that this generalizes.
- Use `@openhands/typescript-client` classes directly for agent-server-backed REST/workspace/event/VS Code calls. Centralize host/session API key/working-directory option assembly through `src/api/agent-server-client-options.ts`; the backend fallback policy itself lives in `src/api/backend-registry/active-store.ts`. - Use `@openhands/typescript-client` classes directly for agent-server-backed REST/workspace/event/VS Code calls. Centralize host/session API key/working-directory option assembly through `src/api/agent-server-client-options.ts`; the backend fallback policy itself lives in `src/api/backend-registry/active-store.ts`.
- Local verification/build gotchas: - Local verification/build gotchas:
- `npm run typecheck` assumes generated translation types exist; run `npm run make-i18n` first if `src/i18n/declaration.ts` is missing. - `npm run typecheck` assumes generated translation types exist; run `npm run make-i18n` first if `src/i18n/declaration.ts` is missing.
+1 -1
View File
@@ -12,7 +12,7 @@
"@heroui/react": "2.8.10", "@heroui/react": "2.8.10",
"@microlink/react-json-view": "1.31.20", "@microlink/react-json-view": "1.31.20",
"@monaco-editor/react": "4.7.0", "@monaco-editor/react": "4.7.0",
"@openhands/typescript-client": "github:OpenHands/typescript-client#ef62e82fc3dfb03991a1c8025429caf354427263", "@openhands/typescript-client": "git+https://github.com/OpenHands/typescript-client.git#ef62e82fc3dfb03991a1c8025429caf354427263",
"@react-router/node": "7.14.2", "@react-router/node": "7.14.2",
"@react-router/serve": "7.14.2", "@react-router/serve": "7.14.2",
"@tailwindcss/vite": "4.2.4", "@tailwindcss/vite": "4.2.4",
+1 -1
View File
@@ -23,7 +23,7 @@
"@heroui/react": "2.8.10", "@heroui/react": "2.8.10",
"@microlink/react-json-view": "1.31.20", "@microlink/react-json-view": "1.31.20",
"@monaco-editor/react": "4.7.0", "@monaco-editor/react": "4.7.0",
"@openhands/typescript-client": "github:OpenHands/typescript-client#ef62e82fc3dfb03991a1c8025429caf354427263", "@openhands/typescript-client": "git+https://github.com/OpenHands/typescript-client.git#ef62e82fc3dfb03991a1c8025429caf354427263",
"@react-router/node": "7.14.2", "@react-router/node": "7.14.2",
"@react-router/serve": "7.14.2", "@react-router/serve": "7.14.2",
"@tailwindcss/vite": "4.2.4", "@tailwindcss/vite": "4.2.4",
+33
View File
@@ -0,0 +1,33 @@
#!/usr/bin/env bash
# Custom Vercel install command.
#
# npm normalizes any GitHub URL it finds in package.json (including
# `git+https://github.com/...` and the `github:owner/repo` shorthand) to
# `git+ssh://git@github.com/...` when it writes package-lock.json. Vercel's
# build environment has no SSH key for GitHub, so npm cannot clone the
# `@openhands/typescript-client` git dependency and silently falls back to a
# stale cached copy — producing the dreaded
# `[MISSING_EXPORT] ConversationClient is not exported by
# node_modules/@openhands/typescript-client/dist/clients.js` at bundle time.
#
# Two defensive measures here:
# 1. Rewrite any `git+ssh://git@github.com/` URLs in package-lock.json
# to `git+https://github.com/` before invoking npm so the lockfile
# Vercel actually consumes is HTTPS-only, regardless of which lockfile
# shape happened to be committed.
# 2. Configure git globally to translate the matching ssh forms into
# https — this catches anything npm has already cached as an ssh URL
# and any future git deps that hit the same bug.
#
# See https://github.com/OpenHands/agent-canvas/issues/384 for the original
# bug report.
set -euo pipefail
if [ -f package-lock.json ]; then
sed -i 's|git+ssh://git@github.com/|git+https://github.com/|g' package-lock.json
fi
git config --global url."https://github.com/".insteadOf "ssh://git@github.com/"
git config --global url."https://github.com/".insteadOf "git@github.com:"
npm ci
+4
View File
@@ -0,0 +1,4 @@
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"installCommand": "bash scripts/vercel-install.sh"
}