Skip to content

fix(tmux-terminal): D7 resolveWorkerPath walks up to find agents/lib/bg-worker (symlink loading)#102

Merged
lantiscooperdev merged 2 commits into
mainfrom
fix/p5-d7-resolve-worker-symlink
Jun 27, 2026
Merged

fix(tmux-terminal): D7 resolveWorkerPath walks up to find agents/lib/bg-worker (symlink loading)#102
lantiscooperdev merged 2 commits into
mainfrom
fix/p5-d7-resolve-worker-symlink

Conversation

@lantisprime

Copy link
Copy Markdown
Owner

Summary

Fixes D7: resolveWorkerPath() production mode looked for bg-worker.ts in the wrong directory (tmux-terminal/ instead of agents/lib/), causing the extension to silently skip registration when loaded via symlink (~/.pi/agent/extensions/tmux-terminal).

Bug surfaced by the user's interactive test — after running ln -s and restarting pi, /agents bg scout "..." still said "No terminal backend installed". This bug was invisible to all 63 unit tests + the real-tmux smoke test (see "Why previous tests missed it" below).

The bug

tmux-terminal/lib/resolve-worker-path.ts computed the production-mode baseDir as:

path.dirname(path.dirname(fileURLToPath(import.meta.url)))

For a module at tmux-terminal/lib/resolve-worker-path.ts, this resolves to tmux-terminal/ — but bg-worker.ts actually lives at agents/lib/bg-worker.ts. The production-mode path looked in the wrong directory and returned null. The extension's registerBgTerminalBackend was silently skipped (console.debug("tmux-terminal: worker not found, skipping registration")), and getBgTerminalBackend() in agents/index.ts returned null, producing the "No terminal backend installed" warning.

The fix

Production mode now walks UP from the module's directory, checking for agents/lib/bg-worker.{ts,mjs,js} at each level until found or root reached. Robust to:

  • Symlinked extensions (~/.pi/agent/extensions/tmux-terminal)
  • Arbitrary repo depth (the agents/ dir may be N levels above)
  • ts vs mjs vs js worker variants
let dir = path.dirname(fileURLToPath(import.meta.url));
const root = path.parse(dir).root;
while (dir !== root) {
  const agentsLibDir = path.join(dir, "agents", "lib");
  for (const basename of WORKER_BASENAMES) {
    const candidate = path.join(agentsLibDir, basename);
    if (fs.existsSync(candidate)) return fs.realpathSync(candidate);
  }
  const parent = path.dirname(dir);
  if (parent === dir) break;
  dir = parent;
}
return null;

New test

testProductionModeFindsWorkerByWalkingUp in test-fixtures/test-helpers.mjs:

  • Sets up a fake repo layout in a tmpdir: agents/lib/bg-worker.ts + fake-extension/lib/{resolve-worker-path,constants}.ts
  • Uses copy (not symlink) so Node ESM doesn't resolve import.meta.url to the real path
  • Spawns a child node process to verify production-mode path resolution
  • Verified to FAIL when the production-mode walk-up is reverted to the broken version

Why previous tests missed it

Test Why it missed D7
testWorkerPathIsRealpathed Uses resolveWorkerPath(searchDir) — bypasses production-mode path
testWorkerPathPrefersTsOverMjs Same — uses searchDir
test-real-tmux-smoke.mjs Passes workerPath directly to createTmuxBackend, never going through resolveWorkerPath
Manual smoke (load both extensions via -e) Worked because Node loads via real path, not symlink — import.meta.url happens to give a path where dirname(dirname(...)) finds agents/lib/

Lesson: any production-mode code that depends on import.meta.url for path computation needs at least one test that simulates symlink loading via copy + child process.

Verification

Files changed (3 files, +60/-5)

  • tmux-terminal/lib/resolve-worker-path.ts — walk-up logic for production mode
  • tmux-terminal/test-fixtures/test-helpers.mjs — new D7 regression test
  • agents/P3_IMPLEMENTATION_SLICES.md — D7 documented in deviations list

User-visible fix

After this PR merges + symlink exists, /agents bg scout "echo hello" should launch a tmux window (no more "No terminal backend installed" warning).

🤖 Generated with pi-coding-agent

…bg-worker (symlink loading)

Bug surfaced by interactive test: when tmux-terminal is loaded via the
~/.pi/agent/extensions symlink, resolveWorkerPath returns null and the
extension silently skips registerBgTerminalBackend — producing
"No terminal backend installed" on /agents bg.

Root cause: production-mode baseDir was computed as
`path.dirname(path.dirname(fileURLToPath(import.meta.url)))`, which for a
module at `tmux-terminal/lib/resolve-worker-path.ts` resolves to
`tmux-terminal/` — but bg-worker.ts actually lives in `agents/lib/`.

Fix: walk UP from the module's directory, checking for
`agents/lib/bg-worker.{ts,mjs,js}` at each level. Robust to:
  - symlinked extensions (~/.pi/agent/extensions/tmux-terminal)
  - arbitrary repo depth (the agents/ dir may be N levels above)
  - ts vs mjs vs js worker variants

Test added (testProductionModeFindsWorkerByWalkingUp in test-helpers.mjs):
  - Sets up a fake repo layout in a tmpdir
  - Copies resolve-worker-path.ts + constants.ts into a fake-extension/lib dir
  - Spawns a child node process to verify production-mode path resolves
  - Uses copy (not symlink) so Node ESM doesn't resolve import.meta.url
    to the real path

Why previous tests missed it:
  - testWorkerPathIsRealpathed + testWorkerPathPrefersTsOverMjs use the
    searchDir parameter, which bypasses production-mode path calc
  - test-real-tmux-smoke.mjs passes workerPath directly to createTmuxBackend,
    never going through resolveWorkerPath
  - The production-mode (no args) path was never exercised in any test

Verification:
  - bash tmux-terminal/test-fixtures/run-p5-tests.sh: 63/63 + REQ-13 OK
  - bash agents/test-fixtures/run-p4-4-tests.sh: green
  - node tmux-terminal/test-fixtures/test-real-tmux-smoke.mjs: 11/11
  - D7 test against broken version: FAILS (regression guard works)
  - Mutation #1 + #2: still discriminate correctly

Surfaces-by pattern: any production-mode code that depends on
import.meta.url for path computation needs at least one test that
simulates the symlink-loading case (via copy + child process, since
Node ESM resolves symlinks at load time).
@lantiscooperdev lantiscooperdev merged commit 6ec1b4f into main Jun 27, 2026
1 check passed
@lantisprime lantisprime deleted the fix/p5-d7-resolve-worker-symlink branch June 27, 2026 09:28
lantiscooperdev pushed a commit that referenced this pull request Jun 27, 2026
…103)

Three-source sync per workplan-update runbook after PR #102 merged:

1. Episodic memory (canonical-workplan): revised to new episode ID
   20260627-092842-p5-fully-shipped-d5-d6-d7-fixes-merged-p-7d1e
   which supersedes the D5+D6-only episode. New body documents D7
   (symlink-loading fix), the full D5/D6/D7 deviation timeline,
   lessons stored globally, and points at P5b as the next natural
   extension.

2. WORKPLAN.md pointer refreshed to the new canonical-workplan episode.
   Tags include p5-pr-98, p5-pr-100, p5-pr-102, p5-d5-d6-fix, p5-d7-fix.

3. agents/P3_IMPLEMENTATION_SLICES.md top-level P5 entry updated:
   - Added PR #102 + commit 6ec1b4b next to PR #100
   - Branches line updated to note all 3 deleted branches

No code changes. No agent/lib/ changes. Pattern follows PR #99 and #101
docs-only sync PRs (also required because main is branch-protected).

Co-authored-by: Charlton D. Ho <dev@znp.pw>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants