Skip to content

feat: in-process Lua core handler for claudecode (#47, phase 3)#63

Merged
Cannon07 merged 3 commits into
mainfrom
phase-3-claudecode-flip
May 19, 2026
Merged

feat: in-process Lua core handler for claudecode (#47, phase 3)#63
Cannon07 merged 3 commits into
mainfrom
phase-3-claudecode-flip

Conversation

@Cannon07
Copy link
Copy Markdown
Owner

Summary

Phase 3 of #47 — ports the bash core handler to in-process Lua and flips the Claude Code backend to use it. Backends still on bash (opencode/codex/copilot/gemini) continue working unchanged; they flip in follow-up PRs.

Architectural choice (see ADR-0005): the new handler runs in-process inside the user's Neovim via a single RPC call, not as a headless worker. This eliminates the per-proposal cold-start (~50–100ms × every Edit / Write / MultiEdit / ApplyPatch) and collapses 5+ RPC round-trips per proposal into one. Issue #47 originally framed phase 3 as a like-for-like swap to a headless worker; the ADR records why we deviated.

What changed

  • New: lua/code-preview/pre_tool/{init,bash_detect,normalisers,emitters}.lua — orchestration, Tier 1 Bash detection, per-backend dispatch tables.
  • New: lua/code-preview/post_tool.lua — post-tool cleanup.
  • New: lua/code-preview/apply/{edit,multi_edit,patch}.lua — extracted from bin/apply-*.lua as pure Lua modules; bin/apply-*.lua now thin shims for external callers.
  • Rewritten: backends/claudecode/code-{preview,close}-diff.sh — one nvim_call into the new in-process handler, abstain (exit 0) when nvim is unreachable.
  • Tests: 41 new specs across tests/plugin/pre_tool_{bash_detect,normaliser,handle}_spec.lua, including regressions for the it\'s-mine.txt apostrophe-escape case (a fix that goes beyond pure bash parity).
  • Docs: ADR-0005 (new), glossary updates to Core handler, Headless worker, Hook context query, ADR-0004 xref.

Behavioural contract

  • Claude Code now goes through in-process Lua.
  • opencode/codex/copilot still go through bin/core-{pre,post}-tool.sh.
  • Both paths land in the same changes / neo_tree / diff modules and serialise on the main loop — no concurrency hazard during the per-backend rollout.
  • When the user's Neovim is unreachable, the shim abstains (exit 0, no stdout): Claude Code falls back to its native permission flow, exactly as if the plugin weren't installed.

Out of scope

  • Windows .cmd / .ps1 shims — tracked in Request Windows 11 support #46, will consume the in-process Lua landed here.
  • Deletion of bin/core-{pre,post}-tool.sh and jq/lsof healthcheck cleanup — happens in a separate cleanup PR after the last backend flips.
  • diff.lua refactor — separate.

Test plan

  • All existing plugin specs still pass (./tests/run_lua.sh): 63 success / 0 fail / 0 error
  • Edit / Write / MultiEdit proposals open a diff and prompt for permission
  • Bash with rm <file> marks the file deleted in neo-tree (no diff tab)
  • Bash with rm it\'s-mine.txt correctly highlights (new beyond bash parity)
  • Bash with echo x > newfile.txt marks bash_created
  • Post-tool closes the diff and clears the change indicator
  • diff.defer_claude_permissions = true suppresses the ask prompt
  • diff.visible_only = true skips diffs for non-visible files

🤖 Generated with Claude Code

Cannon07 and others added 3 commits May 19, 2026 11:55
Replaces bin/core-pre-tool.sh's role for Claude Code with an in-process
Lua orchestrator (lua/code-preview/pre_tool/), invoked through a single
RPC call from the per-OS hook entry. Eliminates the per-proposal nvim
cold-start (~50-100ms) and collapses the 5+ RPC round-trips per
proposal into one. apply-edit / apply-multi-edit / apply-patch move to
lua/code-preview/apply/ and are called in-process; bin/ retains thin
shims for external callers.

The bash core handler stays in place for opencode / codex / copilot;
they flip in follow-up PRs. See docs/adr/0005 for the design rationale,
including why we run in-process rather than as a headless worker.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Three CI failures, all rooted in subtle parity gaps between the new
in-process emitter and the bash core handler:

- vim.json.encode does not preserve table key order; Ubuntu's Lua hash
  seed produced a key order that broke the shell tests' byte-exact JSON
  comparison. Switched the claudecode emitter to a hand-built string
  matching the bash printf format exactly.

- Bash core exited 0 for Bash / ApplyPatch / unknown tools BEFORE
  reaching the permissionDecision printf. The new emitter was firing
  unconditionally. Restricted emission to Edit / Write / MultiEdit and
  threaded tool_name into the emitter context.

- test_stale_socket.sh asserted that the proposed temp file was written
  even when no Neovim was reachable — a side effect of the bash core
  always spawning apply-edit.lua. ADR-0005 deliberately removes that
  side effect (the plugin abstains entirely). Test updated to match
  the new contract.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Six small fixes surfaced in independent code review:

- pre_tool: extract present_single_file() helper from handle_edit /
  handle_write / handle_multi_edit (removed ~30 lines of duplication).
- pre_tool: add sweep_leftover_tempfiles(); call from setup() to clear
  stale /tmp/claude-diff-* across sessions (the bash post-tool did this
  via a global wildcard rm; per-proposal tracking deferred to follow-up).
- emitters: drop dead has_nvim branch (handle() always sets it true).
- claudecode shims: guard against malformed jq output, add comment
  explaining why `set -e` is intentionally omitted.
- post_tool: add post_tool_handle_spec.lua covering Bash status clear,
  concurrent-edit marker preservation, return-value contract, and
  robustness against malformed inputs.

Two review items deferred to a follow-up issue (per discussion):
  * Apply looks_like_path to rm tokens (behavioural change beyond bash
    parity).
  * Per-proposal tempfile tracking (the startup sweep covers the
    cross-session leak; within-session refinement comes later).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@Cannon07 Cannon07 merged commit 0664af6 into main May 19, 2026
2 checks passed
@Cannon07 Cannon07 deleted the phase-3-claudecode-flip branch May 19, 2026 07:23
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