How Codex Plugin Resolves Workspace Roots Across Different Project Structures

The Codex plugin resolves workspace roots by walking up the directory tree to find the top-level Git repository via git rev-parse --show-toplevel, falling back to the current working directory when no Git repository exists.

The workspace root is the canonical anchor point from which the Codex plugin loads configuration files, persists session state, and routes job logs. According to the openai/codex-plugin-cc source code, this resolution is handled by a dedicated utility that abstracts away project-specific layouts, enabling consistent behavior across monorepos, nested repositories, and plain directories.

The Resolution Algorithm

The entry point for workspace discovery is resolveWorkspaceRoot in plugins/codex/scripts/lib/workspace.mjs. This exported function wraps Git detection with a graceful fallback:

// plugins/codex/scripts/lib/workspace.mjs
export function resolveWorkspaceRoot(cwd) {
  try {
    return ensureGitRepository(cwd);
  } catch {
    // Not inside a Git repo – fall back to the supplied cwd
    return cwd;
  }
}

The function attempts to locate a Git repository starting from cwd. If successful, it returns the repository's top-level directory; otherwise, it treats cwd itself as the workspace root.

Git Detection Implementation

The actual Git traversal lives in plugins/codex/scripts/lib/git.mjs within the ensureGitRepository function:

// plugins/codex/scripts/lib/git.mjs (lines 78-88)
function ensureGitRepository(cwd) {
  const result = spawnSync('git', ['rev-parse', '--show-toplevel'], {
    cwd,
    encoding: 'utf-8',
    stdio: ['pipe', 'pipe', 'ignore']
  });
  
  if (result.status !== 0) {
    throw new Error('Not a git repository');
  }
  
  return result.stdout.trim();
}

This executes git rev-parse --show-toplevel, which Git resolves by walking upward until finding a .git directory. The command returns the absolute path of the outermost repository containing cwd.

Behavior Across Project Structures

Because Git's traversal always moves up the filesystem hierarchy, resolveWorkspaceRoot produces predictable results for common project layouts:

Project Structure Resolution Result
Single repository Returns the repository root (same as cwd if already at root)
Monorepo Returns the monorepo root regardless of which package subdirectory you run from
Nested Git repositories Returns the innermost repository root where .git exists
Non-Git directory Returns cwd unchanged as fallback

Monorepo Example

Running from any package within a monorepo resolves to the shared root:

import { resolveWorkspaceRoot } from "./lib/workspace.mjs";

const cwd = "/Users/alex/company-monorepo/packages/api/src";
const root = resolveWorkspaceRoot(cwd);
// → "/Users/alex/company-monorepo"

This ensures all Codex plugin operations—configuration loading, state persistence, and log routing—use a consistent root regardless of which package triggered the command.

Nested Repository Example

When repositories are nested, resolution stops at the first .git encountered:

const cwd = "/Users/alex/outer-project/subrepo/src/components";
const root = resolveWorkspaceRoot(cwd);
// → "/Users/alex/outer-project/subrepo" (inner repo root)

The inner repository boundary takes precedence, isolating the subrepo as its own workspace.

Non-Git Fallback

For directories outside version control:

const cwd = "/tmp/experiment";
const root = resolveWorkspaceRoot(cwd);
// → "/tmp/experiment" (original cwd preserved)

The plugin functions normally, using the supplied directory as its operational root.

Consumers of Workspace Root Resolution

Higher-level plugin scripts consistently import and invoke resolveWorkspaceRoot to establish their working context.

Configuration Loading

In plugins/codex/scripts/stop-review-gate-hook.mjs (lines 145–148), the workspace root locates session-specific configuration:

import { resolveWorkspaceRoot } from "./lib/workspace.mjs";

const workspaceRoot = resolveWorkspaceRoot(process.cwd());
const configPath = join(workspaceRoot, '.codex', 'config.json');

State File Persistence

The session-lifecycle-hook.mjs script (lines 47–53) resolves state storage relative to the workspace root:

const workspaceRoot = resolveWorkspaceRoot(cwd);
const stateDir = join(workspaceRoot, '.codex', 'state');
await ensureDir(stateDir);
const stateFile = join(stateDir, `${sessionId}.json`);

This pattern ensures state files and logs are co-located with their associated project, not scattered across arbitrary filesystem locations.

Key Files in the Resolution Chain

File Responsibility
plugins/codex/scripts/lib/workspace.mjs Exports resolveWorkspaceRoot; defines fallback behavior
plugins/codex/scripts/lib/git.mjs Implements ensureGitRepository; executes git rev-parse --show-toplevel
plugins/codex/scripts/stop-review-gate-hook.mjs Consumes workspace root for configuration resolution
plugins/codex/scripts/session-lifecycle-hook.mjs Consumes workspace root for state file paths

Summary

  • resolveWorkspaceRoot in plugins/codex/scripts/lib/workspace.mjs is the canonical workspace discovery function.
  • Resolution relies on git rev-parse --show-toplevel via ensureGitRepository in plugins/codex/scripts/lib/git.mjs.
  • Git's upward directory traversal enables automatic monorepo support and nested repository isolation.
  • Non-Git directories gracefully fall back to the supplied cwd.
  • All plugin operations—configuration, state, and logs—reference the consistently resolved root regardless of invocation location.

Frequently Asked Questions

What happens if Git is not installed on the system?

The spawnSync call in ensureGitRepository will fail with a non-zero status or throw if the git binary is missing. This error propagates to resolveWorkspaceRoot's catch block, which returns the original cwd as fallback. The plugin continues operating without Git-based root detection.

Can I force a specific workspace root instead of auto-detection?

The source code does not expose an override mechanism. resolveWorkspaceRoot always attempts Git detection first. To use a custom root, you would need to modify plugins/codex/scripts/lib/workspace.mjs or set cwd to your desired root before invoking the plugin.

Why does the monorepo package resolve to the root rather than the package directory?

Git's --show-toplevel command walks upward until finding the .git folder, which in a monorepo exists only at the repository root. This is intentional design: shared configuration, state, and logs remain centralized rather than fragmented across packages.

Does resolution performance vary with directory depth?

The Git command executes in milliseconds regardless of depth, as Git's internal traversal is highly optimized. The spawnSync overhead dominates, making the operation effectively constant-time for practical project sizes.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →