code-yeongyu / code-yeongyu/lazycodex

OMO LSP post-tool-use hook blocks every edit of files outside the session cwd with 'LSP file path must be inside request cwd'

Open
#154 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
3.5k
Forks
216
PR merge metrics
No merged PRs in 30d

Description

## Summary
Every edit to a file outside the Codex session cwd makes the OMO LSP PostToolUse hook emit a blocking feedback block `LSP file path must be inside request cwd: ` instead of running diagnostics, so the model sees this noise (twice per edit in this session) and never gets real diagnostics for the edited file.

## Environment
- LazyCodex version: `lazycodex-ai 4.19.4` (plugin cache `~/.codex/plugins/cache/sisyphuslabs/omo/4.19.4`; same code present in `code-yeongyu/lazycodex@main`)
- Codex version: `codex-cli 0.146.1`
- OS: Linux x86_64 (WSL2), Node v22.23.1
- Install method: installed Codex plugin (OMO 4.19.4)
- Relevant config: session cwd = `/home/th930/Projects/Aoom_HOME/runtime/state/companies/02_Definish/04_agents/00_D-CEO`; edited files under `/home/th930/Projects/Aoom_HOME/runtime/state/companies/02_Definish/01_repo/00_main/...` (outside session cwd)

## Repository Decision
- Target repository: `code-yeongyu/lazycodex`
- Why this belongs there: the failing surface is the bundled OMO `codex-lsp` hook CLI (`plugins/omo/components/lsp` → `@code-yeongyu/codex-lsp`) and its `lsp-daemon` path validation, both distributed by LazyCodex. Clean upstream Codex contains no `omo-lsp` / `codex-lsp` / `lsp-daemon` code.
- LazyCodex evidence (runtime + `$LAZYCODEX_SOURCE_ROOT/lazycodex-source`):
- Repro (see below) reproduces with the shipped bundled CLI at `~/.codex/plugins/cache/sisyphuslabs/omo/4.19.4/components/lsp/dist/cli.js`, and the same code exists in `lazycodex-source/plugins/omo/components/lsp/dist/cli.js` (4.19.4, current `main`).
- Hook entry: `plugins/omo/components/lsp/src/codex-hook.ts` → `runLspDiagnosticsText` → `callDiagnosticsViaDaemon(filePath, { context: codexLspRequestContext() })`; `codexLspRequestContext()` builds the LSP request context from `process.cwd()` (line ~94: `cwd: canonicalCwd` where `canonicalCwd = realpathSync(resolve(cwd))` with `cwd = process.cwd()`).
- Daemon rejects paths outside that cwd: `resolvePathInsideContext` → `if (!isPathInside(cwd, canonical)) throw new LspInvalidPathError(\`LSP file path must be inside request cwd: ${filePath}\`)` (dist line 4123-4130).
- The hook then wraps the thrown error as a blocking block: `runLspPostToolUseHook` returns `{ decision: "block", reason: "LSP diagnostics after editing :\n\n" }` (`codex-hook.ts` lines 115-127).
- Upstream Codex source evidence from `$LAZYCODEX_SOURCE_ROOT/openai-codex-source`:
- Codex core runs each hook process with `.current_dir(cwd)` where `cwd` is `PostToolUseRequest.cwd`, the session working directory (`codex-rs/hooks/src/engine/command_runner.rs:62`; `codex-rs/hooks/src/events/post_tool_use.rs:29` + `:111`).
- No `omo-lsp`, `codex-lsp`, `lsp-daemon`, or LSP path validation exists in `openai/codex`. The bug disappears in clean upstream Codex because the hook itself is LazyCodex-owned.

## Reproduction
1. Start a Codex session whose cwd is NOT an ancestor of the file to edit (e.g. session cwd `.../04_agents/00_D-CEO`, target file `.../01_repo/00_main/apps/webapp/src/App.tsx`).
2. Edit the target file with a mutation tool (e.g. `apply_patch`).
3. Observe the injected hook feedback.

Minimal standalone repro (no real edit required; feeds the same payload shape the hook consumes):

```bash
cd /home/th930/Projects/Aoom_HOME/runtime/state/companies/02_Definish/04_agents/00_D-CEO
node -e "
const { spawnSync } = require('child_process');
const payload = {
session_id: 'test-session',
tool_name: 'apply_patch',
tool_input: {
input: '*** Begin Patch\n*** Update File: /home/th930/Projects/Aoom_HOME/runtime/state/companies/02_Definish/01_repo/00_main/apps/webapp/src/App.tsx\n*** End Patch'
},
tool_response: { isError: false },
};
const cli = '/home/th930/.codex/plugins/cache/sisyphuslabs/omo/4.19.4/components/lsp/dist/cli.js';
const res = spawnSync('node', [cli, 'hook', 'post-tool-use'], { input: JSON.stringify(payload), encoding: 'utf8', timeout: 30000 });
console.log('EXIT:', res.status);
console.log('STDOUT:', res.stdout);
"
```

## Expected Behavior
- Editing a file anywhere the agent legitimately touches (monorepo subdirectories, sibling projects, worktrees outside the session root) should run diagnostics for that file, or silently skip when no language server is configured.
- A path outside the session cwd should not be reported as a blocking "LSP diagnostics after editing" failure on every edit.

## Actual Behavior
The hook emits a blocking block on every such edit:

```text
LSP diagnostics after editing /home/th930/Projects/Aoom_HOME/runtime/state/companies/02_Definish/01_repo/00_main/apps/webapp/src/App.tsx:

LSP file path must be inside request cwd: /home/th930/Projects/Aoom_HOME/runtime/state/companies/02_Definish/01_repo/00_main/apps/webapp/src/App.tsx
```

Observed twice per edit in this session (each `apply_patch` triggers the feedback twice), with no real diagnostics ever produced. A control case editing a file inside the session cwd produces empty output (no block).

## Evidence
- Repro output (above): exit 0, stdout contains the exact blocking JSON with `decision: "block"` and the `LspInvalidPathError` text.
- Control: same payload with a file inside the session cwd (`.../04_agents/00_D-CEO/HANDOFF.md`) yields empty stdout (no block).
- Session log: the identical message was injected after every `apply_patch` in this thread (e.g. after edits to `apps/webapp/src/lib/auth-proxy.test.ts`, `apps/webapp/src/pages/History.route-a11y.test.tsx`, `apps/webapp/src/components/AppShell.tsx`), with the hook output saved under `/tmp/hook_outputs/019fd769-a648-7451-b165-387c00547aa1/`.

## Root Cause
`codexLspRequestContext()` in `plugins/omo/components/lsp/src/codex-hook.ts` hardcodes the hook's own process cwd as the LSP request cwd:

```ts
export function codexLspRequestContext(
env = process.env,
cwd: string = process.cwd(),
): LspRequestContext {
const canonicalCwd = realpathSync(resolve(cwd));
...
return parseLspRequestContext({ cwd: canonicalCwd, ... });
}
```

Codex launches hooks with `current_dir(PostToolUseRequest.cwd)` (`openai-codex-source/codex-rs/hooks/src/engine/command_runner.rs:62`), so `process.cwd()` equals the session cwd. The daemon then enforces that every edited file lives inside that cwd (`resolvePathInsideContext`, dist line 4123-4130). Editing a file outside the session cwd therefore always throws, and `runLspPostToolUseHook` surfaces the throw as a blocking diagnostics block instead of treating it as "no diagnostics available".

The cwd restriction is reasonable for daemon path safety, but the hook must not (a) derive the LSP context from the hook process cwd when the edited file is outside it, and (b) must not surface an `LspInvalidPathError` as a blocking diagnostics failure.

## Proposed Fix
In `plugins/omo/components/lsp/src/codex-hook.ts` (or the `lsp-core` post-edit collector):
1. Resolve each mutated file path relative to a context cwd that actually contains it, or pass an explicit context whose cwd is the common ancestor of the edited files, instead of blindly using `process.cwd()`.
2. Treat `LspInvalidPathError` (and any "outside cwd" failure) as a non-blocking "skipped" outcome in `collectPostEditDiagnostics` / `runLspPostToolUseHook` — filter it out like the existing `isLspDaemonUnreachableDiagnostics` filter (codex-hook.ts line 113) — so legitimate edits outside the session cwd do not inject noise and do not suppress diagnostics for in-cwd files.

Likely files:
- `plugins/omo/components/lsp/src/codex-hook.ts` (`codexLspRequestContext`, `runLspPostToolUseHook` filter)
- `plugins/omo/components/lsp/src/codex-hook-cli.ts` (hook stdin wiring)
- `lsp-core` `post-edit` collector (`collectPostEditDiagnostics`) and the daemon `resolvePathInsideContext` error handling in `lsp-daemon`

## Verification Plan
- Repro check that fails before the fix: run the standalone repro above; assert stdout contains `LSP file path must be inside request cwd`.
- Fix check: same repro with a file outside the session cwd now yields empty stdout (no block) or real diagnostics; a file inside the cwd still yields diagnostics when a language server is configured.
- Regression check: edits inside the session cwd still produce normal post-edit diagnostics; no new blocking noise is injected for any mutation tool.

---
This issue or PR was generated by LazyCodex.
Tag: lazycodex-generated

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.