Skip to content

fix(auto): cannot resolve gsd-pi/package.json from deployed extension path on resume #3949

@r00t-creditninja

Description

@r00t-creditninja

Problem

Auto-mode crashes on resume with:

Extension "command:gsd" error: Cannot find module 'gsd-pi/package.json'
Require stack:
- ~/.gsd/agent/extensions/gsd/auto.js

This happens every time auto-mode tries to resume (e.g. after a pause or restart), making auto-mode unable to recover gracefully. The error is thrown before the resume path completes, so state is left inconsistent.

Root Cause

auto.ts line 1342 uses createRequire(import.meta.url) to resolve gsd-pi/package.json, but gsd-pi is not findable from the extension's deployed location at ~/.gsd/agent/extensions/gsd/auto.js.

// auto.ts:1341–1343
const _req = createRequire(import.meta.url);
const pkgRoot = dirname(_req.resolve("gsd-pi/package.json")); // ← THROWS
const { initResources } = await import(join(pkgRoot, "dist", "resource-loader.js"));

Why this fails:

loader.js sets NODE_PATH to gsd-pi/node_modules (its own deps, for playwright etc.):

// loader.js:103–107
const gsdNodeModules = join(gsdRoot, 'node_modules');
process.env.NODE_PATH = [gsdNodeModules, process.env.NODE_PATH]
    .filter(Boolean)
    .join(delimiter);

resource-loader.js creates a symlink ~/.gsd/agent/node_modules → gsd-pi/node_modules for ESM resolution.

When createRequire resolves from ~/.gsd/agent/extensions/gsd/auto.js, it walks up:

  1. ~/.gsd/agent/extensions/gsd/node_modules/gsd-pi — absent
  2. ~/.gsd/agent/extensions/node_modules/gsd-pi — absent
  3. ~/.gsd/agent/node_modules/gsd-pisymlink resolves to gsd-pi/node_modules/gsd-pi — absent (gsd-pi is not a dependency of itself)
  4. NODE_PATH entry gsd-pi/node_modules/gsd-pi — absent

gsd-pi is only installed at the global node_modules level, which is NOT in NODE_PATH or any ancestor of the extension path.

The inline comment claims this approach "works in both source and deployed" — this is incorrect for deployments where gsd-pi is installed globally and not self-referencing.

The regression test resource-loader-import-path.test.ts only checks that the resolve("gsd-pi/package.json") pattern exists in source; it does not test that the resolution actually succeeds at runtime.

Expected Behavior

auto.ts should derive pkgRoot using a mechanism that works from the deployed extension path. Two viable options:

Option A (recommended) — use GSD_WORKFLOW_PATH (already set by loader.js):

// GSD_WORKFLOW_PATH = {gsdRoot}/dist/resources/GSD-WORKFLOW.md
// so dirname twice gives us gsdRoot
const pkgRoot = resolve(dirname(process.env.GSD_WORKFLOW_PATH!), '..', '..');
const { initResources } = await import(join(pkgRoot, "dist", "resource-loader.js"));

Option B — expose GSD_PKG_ROOT from loader.js:

// loader.js — add after existing env var assignments
process.env.GSD_PKG_ROOT = gsdRoot; // gsdRoot is already computed at line 10
// auto.ts — use it directly
const pkgRoot = process.env.GSD_PKG_ROOT ?? dirname(_req.resolve("gsd-pi/package.json"));

Option A requires no change to loader.js; Option B is more explicit and self-documenting.

Environment

  • GSD version: 2.69.0
  • Model: claude-sonnet-4-6
  • Failing in: auto-mode resume path (auto.ts handleResume / resumeAuto)

Reproduction Context

Occurs on any auto-mode resume after a pause or restart when gsd-pi is installed via npm/npx globally (the standard installation method). The code path at auto.ts:1342 is only reached during resume, not during initial start.

Forensic Evidence

  • Error message: Cannot find module 'gsd-pi/package.json' from auto.js
  • NODE_PATH observed in process env: {gsd-pi}/node_modules (not the parent directory containing gsd-pi)
  • ~/.gsd/agent/node_modules symlink confirms it points to gsd-pi/node_modules, not the directory containing gsd-pi
  • GSD_WORKFLOW_PATH is reliably set and resolves to {pkgRoot}/dist/resources/GSD-WORKFLOW.md, making it a suitable anchor
  • Downstream effect: resume-time initResources is never called, so bundled extension files are not re-synced; stuck-loop anomalies in forensics may be partially attributable to stale extension state after resume

Auto-generated by /gsd forensics

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions