Skip to content

fix(workspace): canonicalize tmp paths, dynamic config path in serve warning - #1057

Merged
namastex888 merged 1 commit into
devfrom
fix/istemppath-symlink-resolution
Apr 5, 2026
Merged

namastex888 merged 1 commit into
devfrom
fix/istemppath-symlink-resolution

Conversation

@namastex888

Copy link
Copy Markdown
Contributor

Follow-up to #1056 addressing two review comments from gemini-code-assist bot.

#1056 was already merged before the review comments landed, so these fixes ship as a separate small PR.

Comment 1 — isTempPath didn't handle symlinked temp dirs (HIGH)

On macOS, /tmp is often a symlink to /private/tmp. Since path.resolve() does not follow symlinks, isTempPath may return false for paths accessed via their non-canonical form, bypassing the guard.

Accepted. Real cross-platform bug. os.tmpdir() on macOS returns /var/folders/.../T/, while user-facing paths often come through /tmp (symlink). path.resolve() only normalizes .. and makes paths absolute — it does not canonicalize symlinks. A literal /tmp/foo would not prefix-match /var/folders/.../T/, so the guard against writing test workspaces into the global ~/.genie/config.json could be silently bypassed on macOS.

Fix in src/lib/workspace.ts:

  • Added realpathSync to the existing node:fs top-level import (cleaner than the bot's suggested dynamic require).
  • isTempPath now canonicalizes both tmpdir() and root via realpathSync before comparing.
  • Fails closed on canonicalization errors — if the path doesn't exist or can't be resolved, treat it as temp and refuse to persist. The whole point of the guard is to be conservative about what lands in the global config; failing open would defeat it.

Comment 2 — warning message hardcoded ~/.genie/config.json (LOW)

The warning message hardcodes ~/.genie/config.json, which may be inaccurate if GENIE_HOME is set.

Accepted. Cosmetic accuracy. Users who set GENIE_HOME (CI, test harnesses, multi-workspace setups) would see a misleading fix suggestion.

Fix in src/term-commands/serve.ts:

  • Exported genieHome() from workspace.ts (was private).
  • startAgentSync() now resolves the config path dynamically: join(genieHome(), 'config.json').

Tests added

  • Symlink regression test in workspace.test.ts: creates a real symlink within the test's tmpdir() pointing at a real workspace dir, calls findWorkspace(symlinkPath), and asserts nothing was persisted to the isolated fake GENIE_HOME config. Skips gracefully (returns early) on CI environments that disallow symlink creation — we'd rather no coverage than a flaky test.
  • genieHome() env override test: sanity check that the newly-exported helper honors process.env.GENIE_HOME dynamically, so callers in other modules (e.g. the serve warning) see the same value as workspace.ts does internally.

Verification

  • bun test src/lib/workspace.test.ts → 14 pass, 0 fail (was 12)
  • bun test src/lib/ → 1573 pass, 0 fail (was 1571, +2 from this PR)
  • bunx tsc --noEmit → clean
  • bunx biome check on changed files → 0 errors, 1 pre-existing warning on startForeground complexity (unchanged, not introduced here)
  • Pre-push hook ran full 2075-test suite: 0 fail

Test plan

  • Workspace tests pass (including new symlink regression test)
  • Full lib tests pass
  • Type-check clean
  • Biome clean on changed files
  • Manual verification on macOS: create a literal /tmp/test-workspace/.genie/workspace.json, run findWorkspace('/tmp/test-workspace'), confirm ~/.genie/config.json does NOT receive the path (previously would have been persisted on macOS)
  • Manual: start genie serve with GENIE_HOME=/custom/path from outside a workspace → warning should show /custom/path/config.json not ~/.genie/config.json

…path in serve warning

Follow-up to #1056 addressing two review comments from gemini-code-assist.

1. isTempPath() now uses realpathSync to canonicalize both tmpdir() and the
   candidate root before prefix-comparing. On macOS /tmp is a symlink to
   /private/tmp and tmpdir() returns /var/folders/..., so the previous
   path.resolve()-only check would miss any literal /tmp/... path and bypass
   the guard against poisoning the global workspaceRoot config. Fails CLOSED
   on canonicalization errors (treats as temp → refuses to persist) since the
   whole purpose of this guard is to be conservative.

2. startAgentSync() now builds its "no workspace found" warning using
   genieHome() (newly exported) instead of hardcoding ~/.genie/config.json,
   so users who set GENIE_HOME see the actual path in the fix guidance.

Also adds:
- A regression test using a real symlink within the test's tmpdir to verify
  canonicalization works end-to-end (skips gracefully on CI environments
  that disallow symlinks).
- A sanity test that genieHome() honors the GENIE_HOME env override.
@coderabbitai

coderabbitai Bot commented Apr 5, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8f7a5d85-7232-49e2-9bfc-f1f5c8a80b44

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/istemppath-symlink-resolution

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist gemini-code-assist Bot 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.

Code Review

This pull request improves workspace detection by using realpathSync to canonicalize paths in isTempPath, which prevents bypassing the temporary path guard on systems like macOS where /tmp is a symlink. It also exports genieHome for consistent configuration path resolution and adds regression tests. Feedback focuses on removing redundant dynamic imports and resolving variable shadowing in src/term-commands/serve.ts.

async function startAgentSync(): Promise<{ close: () => void } | null> {
try {
const { findWorkspace } = require('../lib/workspace.js') as typeof import('../lib/workspace.js');
const { findWorkspace, genieHome } = require('../lib/workspace.js') as typeof import('../lib/workspace.js');

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.

medium

Importing genieHome here shadows the local genieHome function defined at line 31. Since both functions have the same implementation, this import is redundant. To improve maintainability, you should consider removing the local definition at line 31 and using the exported one from workspace.ts throughout the file, but for now, removing it from this import will resolve the shadowing. This dynamic import pattern is consistent with the repository's approach to breaking circular dependencies.

Suggested change
const { findWorkspace, genieHome } = require('../lib/workspace.js') as typeof import('../lib/workspace.js');
const { findWorkspace } = require('../lib/workspace.js') as typeof import('../lib/workspace.js');
References
  1. Use dynamic imports (require() or import()) to break circular dependency cycles that would otherwise occur at module load time.

Comment on lines +377 to +378
const { join } = require('node:path') as typeof import('node:path');
const configPath = join(genieHome(), 'config.json');

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.

medium

The join function is already imported from node:path at the top of the file (line 23). This dynamic require is redundant and can be removed since the module is already loaded at the top level.

Suggested change
const { join } = require('node:path') as typeof import('node:path');
const configPath = join(genieHome(), 'config.json');
const configPath = join(genieHome(), 'config.json');

@namastex888
namastex888 merged commit c91f2bf into dev Apr 5, 2026
6 checks passed
@namastex888
namastex888 deleted the fix/istemppath-symlink-resolution branch April 10, 2026 16:18
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.

1 participant