[Bug]: senpi LSP user config (~/.pi/lsp-client.json) is migrated away by the brand-dir migration, silently disabling custom LSP servers

Author: itokun99Created Sep 16, 2026Updated Sep 16, 2026

Prerequisites

  • I will write this issue in English
  • I have searched existing issues to avoid duplicates — related: #8039 (same migration, agent dir), #6327 and #6486 (same LSP config/timeout area)
  • I am using the latest version: reproduced on omo-ai 5.0.0-0.beta.63; the hardcoded path is still present in 5.0.0-0.beta.65
  • I have read the documentation and could not find the answer

Environment

  • macOS 15 (Darwin 25.6.0), arm64 (Apple M2)
  • omo-ai 5.0.0-0.beta.63, @code-yeongyu/senpi 2026.9.15-2
  • bash-language-server 5.6.0 (Homebrew), node v24.18.0

Summary

Two components disagree about where the senpi LSP user config lives: the LSP adapter reads ~/.pi/lsp-client.json (hardcoded), while the engine's migrateLegacySenpiDirs moves ~/.pi/* into ~/.omo/*. A user's custom LSP server definitions are therefore relocated to a path the reader never consults, and they silently stop applying. Builtins keep working, which hides the failure. Same migration as #8039, different (and currently unreported) consequence.

Steps to reproduce

  1. Put a custom (non-builtin) server in ~/.pi/lsp-client.json:
    {"lsp": {"probe-custom": {"command": ["/nonexistent/probe-binary"], "extensions": [".zztest"]}}}
    
  2. Request diagnostics for a .zztest file (senpi lsp_diagnostics). The config is read — the server is resolved and the spawn is attempted: LSP server 'probe-custom' for .zztest is NOT INSTALLED. Command not found: /nonexistent/probe-binary
  3. Launch omo/senpi with cwd = $HOME. It prints: Migrated missing project config directory entries /Users/<user>/.pi → /Users/<user>/.omo and ~/.pi is left empty.
  4. Repeat the same diagnostics request → No LSP server configured for extension: .zztest. The config is no longer read.
  5. Placing the same file at ~/.omo/lsp-client.json (the branded dir) does not restore it — the senpi LSP adapter never looks there.

Actual behaviour

  • ~/.omo/lsp-client.json is ignored by the senpi LSP adapter (step 5).
  • ~/.pi/lsp-client.json is read, but the migration empties ~/.pi on every launch from $HOME. The file only survives by accident if the destination already holds a same-named entry (the migration skips entries whose target exists).

Expected behaviour

One of:

  • the senpi LSP adapter resolves the user/project config under CONFIG_DIR_NAME (.omo) with .pi as a read-only fallback, or
  • the migration does not move files the branded layout does not own (e.g. skip lsp-client.json), or copies instead of moves — mirroring brand-dir-migration.ts's deliberate "COPIED once — never moved" treatment of ~/.senpi.

Root cause (code)

  • Reader: omo-ai/plugin/extensions/omo.js (bundled) builds the per-call context with projectConfigPaths:[join(cwd, ".pi", "lsp-client.json")] and userConfigPath: join(HOME, ".pi", "lsp-client.json"); upstream packages/omo-senpi/src/components/lsp/AGENTS.md documents the same.
  • Mover: @code-yeongyu/senpi/dist/legacy-senpi-dir-migration.jsmigrateLegacySenpiDirs(cwd). With cwd = $HOME it renames entries from ~/.pi into ~/.omo, printing the warning only when something actually moved. renameSync preserves mtime, so the relocated file keeps its old timestamp.

Secondary findings (same area)

  1. A custom command that omits the server's subcommand args blocks for the full timeout. With "command": ["/path/bash-lsp"] (missing start) the call ends with LSP daemon request timed out after 30000ms, while daemon.log only records LSP server bash-node24 ... exited with code 0. Adding "start" resolved it. A fast, actionable error (ideally echoing the spawned argv) would prevent this — related to #6486.
  2. The guidance message prints the same path twice: Configure a custom server in '/Users/<user>/.pi/lsp-client.json' or '/Users/<user>/.pi/lsp-client.json'.
  3. Documented paths disagree: the lsp-setup skill points at ~/.codex/lsp-client.json, the OpenCode/omo translator uses <cwd>/.opencode/lsp.json / <cwd>/.omo/lsp.json / <cwd>/.omo/lsp-client.json, and the senpi adapter uses ~/.pi/lsp-client.json. For omo users the branded ~/.omo/lsp-client.json is a natural-looking but dead location.
  4. Not a bug, but easy to misread: for a builtin id, the user config's command is ignored. Verified: {"bash": {"command": ["/path/bash-lsp"], ...}} still spawned bash-language-server start. Docs state this for project configs and for fully custom servers; the senpi user-config case is not spelled out (#6327 covers the same gap for OpenCode).

Impact

Custom LSP servers and command overrides (e.g. pinning a node keg, adding --query-driver) silently stop applying after the first launch from $HOME. No error surfaces and builtins mask it, so a carefully configured LSP can be lost without the user noticing.

Source: code-yeongyu/oh-my-openagent