From 9446d40bfc7ade0ca49fa0e4f9494c7fd810f809 Mon Sep 17 00:00:00 2001 From: Thomas Kosiewski Date: Thu, 12 Nov 2026 03:00:28 +0000 Subject: [PATCH 1/3] docs: say the lock file stays when its removal fails lockfile.remove can fail (a read-only lock directory, or a file that is already gone). stop and the start token mismatch only log a warning then, so the lock file stays (init.lua:642-647, :605-608). Three sentences in scripts/README.md said the plugin removes the lock without that condition. The lock directory sentence now says when the default applies: when CLAUDE_CONFIG_DIR is unset or empty, as README.md already does. --- scripts/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/README.md b/scripts/README.md index efe986894..0d12fb990 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -87,9 +87,9 @@ The Lua repros run from the repository root with `nvim --headless -u NONE -l scr - `run_integration_tests_individually.sh` creates two `mktemp` files and two `mktemp -d` folders per test (the output, the process group id, and the `XDG_STATE_HOME` and `XDG_CONFIG_HOME` that the file's two Neovims share) and deletes all four after the file ran. When it gets SIGTERM it kills the process group of the running file, deletes them and exits 143. It sets `VIMINIT`, `CLAUDECODE_MINIMAL_INIT`, `XDG_STATE_HOME` and `XDG_CONFIG_HOME` for that file's Neovims and the processes they start only, as a prefix of one command, and leaves `XDG_DATA_HOME` alone because plenary lives there. A run writes nothing under `~/.local/state/nvim`, and the developer's `ftplugin` and `after` files under `~/.config/nvim` do not run [run_integration_tests_individually.sh:temp_output, group_file, state_dir, config_dir]. - `repro_issue_231.lua` deletes `/__issue_231_repro__.md` [repro_issue_231.lua:try_open_diff]. - `repro_issue_70.sh` and `repro_issue_277.sh` make a work dir with `mktemp -d` (`${TMPDIR:-/tmp}/issue70.XXXXXX` or `issue277.XXXXXX`) and an `att-home` folder in it before they check their tools. They install `trap cleanup EXIT` after that check, so an exit 3 for a missing tool leaves the work dir behind. The trap also runs on SIGTERM, SIGINT and SIGHUP. It destroys the `agent-tty` sessions and removes the work dir. agent-tty still leaves an empty socket folder `/tmp/agent-tty/<8 hex digits>` (it ignores `TMPDIR`) and an entry for the deleted home in `${XDG_STATE_HOME:-$HOME/.local/state}/agent-tty/homes.json`, one of each per run. The sessions inherit the caller's environment, so `CLAUDE_CONFIG_DIR` and the `XDG_*` variables reach the Neovim and the `claude` CLI that they run. `repro_issue_70.sh` launches the real `claude` CLI, and it also runs `claude --version` once outside agent-tty. Its cleanup runs `pkill -f "repro_issue_70_probe.lua $REPO_ROOT"`, which also kills the probe of another run in the same checkout [repro_issue_70.sh:WORK, cleanup] [repro_issue_277.sh:WORK, cleanup]. -- `repro_issue_70_probe.lua` starts the real server with `auto_start = true`, so the plugin writes a real `PORT.lock` into the user's lock directory. Other Claude CLIs can see that lock file until `claudecode.stop()` removes it at the end [repro_issue_70_probe.lua:claudecode.setup, claudecode.stop]. -- `repro_issue_70.sh` starts one background `nvim --headless -u NONE -l repro_issue_70_probe.lua` per scenario, three in all, with output sent to `/dev/null`. Each probe writes `.json` in the work dir and stops when the script touches `.stop`, after 120 s, or when cleanup sends it SIGTERM. A probe stopped by SIGTERM still removes its `PORT.lock`, but a SIGKILL leaves the lock file behind. Scenarios B and C set `http_proxy`, `https_proxy` and `all_proxy` to `http://127.0.0.1:1`, a closed port, in the agent-tty shell [repro_issue_70.sh:start_probe, run_scenario, DEAD_PROXY]. -- `repro_issue_277.sh` starts Neovim with the `issue-277` fixture in a detached agent-tty session. The fixture calls `claudecode.setup` with `auto_start = true`, so the plugin writes a real `PORT.lock` into the lock directory (`$CLAUDE_CONFIG_DIR/ide`, else `~/.claude/ide`) while the script runs. Other Claude CLIs can see that lock file. Neovim also listens on `nvim.sock` in the work dir. Cleanup runs `agent-tty destroy`, Neovim exits, and the plugin removes the lock. When the script dies by SIGKILL, the trap does not run, so the agent-tty host, both Neovim processes, the lock file and the work dir stay until you run `agent-tty --home /att-home destroy ` [fixtures/issue-277/init.lua:claudecode.setup] [repro_issue_277.sh:NVIM_SOCK, cleanup]. +- `repro_issue_70_probe.lua` starts the real server with `auto_start = true`, so the plugin writes a real `PORT.lock` into the user's lock directory. Other Claude CLIs can see that lock file until `claudecode.stop()` removes it at the end. When the removal fails, `stop` only logs a warning and the file stays [repro_issue_70_probe.lua:claudecode.setup, claudecode.stop] [lua/claudecode/init.lua:M.stop]. +- `repro_issue_70.sh` starts one background `nvim --headless -u NONE -l repro_issue_70_probe.lua` per scenario, three in all, with output sent to `/dev/null`. Each probe writes `.json` in the work dir and stops when the script touches `.stop`, after 120 s, or when cleanup sends it SIGTERM. A probe stopped by SIGTERM still tries to remove its `PORT.lock` (when that fails, `stop` only logs a warning and the file stays), but a SIGKILL leaves the lock file behind. Scenarios B and C set `http_proxy`, `https_proxy` and `all_proxy` to `http://127.0.0.1:1`, a closed port, in the agent-tty shell [repro_issue_70.sh:start_probe, run_scenario, DEAD_PROXY]. +- `repro_issue_277.sh` starts Neovim with the `issue-277` fixture in a detached agent-tty session. The fixture calls `claudecode.setup` with `auto_start = true`, so the plugin writes a real `PORT.lock` into the lock directory (`$CLAUDE_CONFIG_DIR/ide`, or `~/.claude/ide` when that variable is unset or empty) while the script runs. Other Claude CLIs can see that lock file. Neovim also listens on `nvim.sock` in the work dir. Cleanup runs `agent-tty destroy`, Neovim exits, and the plugin removes the lock, or only logs a warning when it cannot. When the script dies by SIGKILL, the trap does not run, so the agent-tty host, both Neovim processes, the lock file and the work dir stay until you run `agent-tty --home /att-home destroy ` [fixtures/issue-277/init.lua:claudecode.setup] [repro_issue_277.sh:NVIM_SOCK, cleanup]. - `repro_issue_277.sh` clones diffview.nvim with `git clone --depth=1` from GitHub into `${XDG_DATA_HOME:-$HOME/.local/share}/issue-277/diffview.nvim` when that folder is absent, so the first run needs the network. The clone is not pinned, and the script keeps it after exit. Its Neovim runs with `NVIM_APPNAME=issue-277`, so it writes its ShaDa file and logs under `${XDG_STATE_HOME:-$HOME/.local/state}/issue-277/`, and diffview.nvim writes `${XDG_CACHE_HOME:-$HOME/.cache}/issue-277/diffview.log`. The script ends each `nvim --remote-expr` call after 10 s and each `websocat` call after 15 s with `perl -e 'alarm N; exec ...'`, and it sleeps with `perl`, because macOS has no `timeout` [repro_issue_277.sh:DIFFVIEW_DIR, rexpr, mcp_call]. - When `ISSUE277_ARTIFACTS` is unset, the screen snapshots go to `artifacts/` in the work dir, and cleanup removes them. A run that reaches its summary (exit 0 or 1) first copies them with `cp -R` to `${TMPDIR:-/tmp}/issue277-artifacts-latest`. When that folder already exists, the copy lands in `issue277-artifacts-latest/artifacts/`, and the files at the top level stay from the first run. A setup failure (exit 2) copies nothing. When `ISSUE277_ARTIFACTS` is set, the script creates that folder before its tool check and keeps it [repro_issue_277.sh:ARTIFACTS, snap]. - `repro_issue_218.sh` runs `Lazy! sync`, which downloads fixture plugins. It does not remove its temp directory [repro_issue_218.sh:cleanup]. From 06ae6bcab12a25b4a6fc38a8589a73d8dd17842a Mon Sep 17 00:00:00 2001 From: Thomas Kosiewski Date: Thu, 12 Nov 2026 03:26:55 +0000 Subject: [PATCH 2/3] docs: qualify the lock file removal in ARCHITECTURE.md The Resource Cleanup comment said M.stop() removes the lock file. It tries to, and only logs a warning when it cannot (init.lua:642-647). The Lock File System section reads as how the plugin works today, but lockfile.lua has no VimLeavePre autocmd and removes the file with os.remove through lockfile.remove. One sentence after the sketch says where the real cleanup runs and that stop only warns when the removal fails. --- ARCHITECTURE.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 5a6b0d90e..5bb81104b 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -57,6 +57,8 @@ vim.api.nvim_create_autocmd("VimLeavePre", { }) ``` +The real cleanup runs from `init.lua`: its `VimLeavePre` autocmd calls `stop` when the server runs, `stop` calls `lockfile.remove`, and `stop` only logs a warning when the removal fails, so the lock file stays. + ### 3. MCP Tool System (`tools/`) Dynamic tool registration with JSON schema validation: @@ -172,7 +174,7 @@ Automatic cleanup on shutdown: ```lua vim.api.nvim_create_autocmd("VimLeavePre", { callback = function() - M.stop() -- Stop server, remove lock file + M.stop() -- Stop server, try to remove lock file (only logs a warning when it cannot) end }) ``` From f300b5ee901882832de2d36bb90bac678130b0f7 Mon Sep 17 00:00:00 2001 From: Thomas Kosiewski Date: Thu, 12 Nov 2026 03:41:07 +0000 Subject: [PATCH 3/3] docs: say that a lock file the removal failed to delete stays Codex P3: lockfile.remove also returns an error when the lock file is already gone (lockfile.lua:215-216), and stop logs the same warning then. A failed removal therefore does not always leave a file. Three sentences said that the file stays. They now say that a lock file that the removal failed to delete stays. --- ARCHITECTURE.md | 2 +- scripts/README.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 5bb81104b..d9b66a1bf 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -57,7 +57,7 @@ vim.api.nvim_create_autocmd("VimLeavePre", { }) ``` -The real cleanup runs from `init.lua`: its `VimLeavePre` autocmd calls `stop` when the server runs, `stop` calls `lockfile.remove`, and `stop` only logs a warning when the removal fails, so the lock file stays. +The real cleanup runs from `init.lua`: its `VimLeavePre` autocmd calls `stop` when the server runs, `stop` calls `lockfile.remove`, and `stop` only logs a warning when the removal fails, so a lock file that the removal failed to delete stays. ### 3. MCP Tool System (`tools/`) diff --git a/scripts/README.md b/scripts/README.md index 0d12fb990..38efc43b8 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -87,8 +87,8 @@ The Lua repros run from the repository root with `nvim --headless -u NONE -l scr - `run_integration_tests_individually.sh` creates two `mktemp` files and two `mktemp -d` folders per test (the output, the process group id, and the `XDG_STATE_HOME` and `XDG_CONFIG_HOME` that the file's two Neovims share) and deletes all four after the file ran. When it gets SIGTERM it kills the process group of the running file, deletes them and exits 143. It sets `VIMINIT`, `CLAUDECODE_MINIMAL_INIT`, `XDG_STATE_HOME` and `XDG_CONFIG_HOME` for that file's Neovims and the processes they start only, as a prefix of one command, and leaves `XDG_DATA_HOME` alone because plenary lives there. A run writes nothing under `~/.local/state/nvim`, and the developer's `ftplugin` and `after` files under `~/.config/nvim` do not run [run_integration_tests_individually.sh:temp_output, group_file, state_dir, config_dir]. - `repro_issue_231.lua` deletes `/__issue_231_repro__.md` [repro_issue_231.lua:try_open_diff]. - `repro_issue_70.sh` and `repro_issue_277.sh` make a work dir with `mktemp -d` (`${TMPDIR:-/tmp}/issue70.XXXXXX` or `issue277.XXXXXX`) and an `att-home` folder in it before they check their tools. They install `trap cleanup EXIT` after that check, so an exit 3 for a missing tool leaves the work dir behind. The trap also runs on SIGTERM, SIGINT and SIGHUP. It destroys the `agent-tty` sessions and removes the work dir. agent-tty still leaves an empty socket folder `/tmp/agent-tty/<8 hex digits>` (it ignores `TMPDIR`) and an entry for the deleted home in `${XDG_STATE_HOME:-$HOME/.local/state}/agent-tty/homes.json`, one of each per run. The sessions inherit the caller's environment, so `CLAUDE_CONFIG_DIR` and the `XDG_*` variables reach the Neovim and the `claude` CLI that they run. `repro_issue_70.sh` launches the real `claude` CLI, and it also runs `claude --version` once outside agent-tty. Its cleanup runs `pkill -f "repro_issue_70_probe.lua $REPO_ROOT"`, which also kills the probe of another run in the same checkout [repro_issue_70.sh:WORK, cleanup] [repro_issue_277.sh:WORK, cleanup]. -- `repro_issue_70_probe.lua` starts the real server with `auto_start = true`, so the plugin writes a real `PORT.lock` into the user's lock directory. Other Claude CLIs can see that lock file until `claudecode.stop()` removes it at the end. When the removal fails, `stop` only logs a warning and the file stays [repro_issue_70_probe.lua:claudecode.setup, claudecode.stop] [lua/claudecode/init.lua:M.stop]. -- `repro_issue_70.sh` starts one background `nvim --headless -u NONE -l repro_issue_70_probe.lua` per scenario, three in all, with output sent to `/dev/null`. Each probe writes `.json` in the work dir and stops when the script touches `.stop`, after 120 s, or when cleanup sends it SIGTERM. A probe stopped by SIGTERM still tries to remove its `PORT.lock` (when that fails, `stop` only logs a warning and the file stays), but a SIGKILL leaves the lock file behind. Scenarios B and C set `http_proxy`, `https_proxy` and `all_proxy` to `http://127.0.0.1:1`, a closed port, in the agent-tty shell [repro_issue_70.sh:start_probe, run_scenario, DEAD_PROXY]. +- `repro_issue_70_probe.lua` starts the real server with `auto_start = true`, so the plugin writes a real `PORT.lock` into the user's lock directory. Other Claude CLIs can see that lock file until `claudecode.stop()` removes it at the end. When the removal fails, `stop` only logs a warning, so a lock file that the removal failed to delete stays [repro_issue_70_probe.lua:claudecode.setup, claudecode.stop] [lua/claudecode/init.lua:M.stop]. +- `repro_issue_70.sh` starts one background `nvim --headless -u NONE -l repro_issue_70_probe.lua` per scenario, three in all, with output sent to `/dev/null`. Each probe writes `.json` in the work dir and stops when the script touches `.stop`, after 120 s, or when cleanup sends it SIGTERM. A probe stopped by SIGTERM still tries to remove its `PORT.lock` (when that fails, `stop` only logs a warning, so a lock file that the removal failed to delete stays), but a SIGKILL leaves the lock file behind. Scenarios B and C set `http_proxy`, `https_proxy` and `all_proxy` to `http://127.0.0.1:1`, a closed port, in the agent-tty shell [repro_issue_70.sh:start_probe, run_scenario, DEAD_PROXY]. - `repro_issue_277.sh` starts Neovim with the `issue-277` fixture in a detached agent-tty session. The fixture calls `claudecode.setup` with `auto_start = true`, so the plugin writes a real `PORT.lock` into the lock directory (`$CLAUDE_CONFIG_DIR/ide`, or `~/.claude/ide` when that variable is unset or empty) while the script runs. Other Claude CLIs can see that lock file. Neovim also listens on `nvim.sock` in the work dir. Cleanup runs `agent-tty destroy`, Neovim exits, and the plugin removes the lock, or only logs a warning when it cannot. When the script dies by SIGKILL, the trap does not run, so the agent-tty host, both Neovim processes, the lock file and the work dir stay until you run `agent-tty --home /att-home destroy ` [fixtures/issue-277/init.lua:claudecode.setup] [repro_issue_277.sh:NVIM_SOCK, cleanup]. - `repro_issue_277.sh` clones diffview.nvim with `git clone --depth=1` from GitHub into `${XDG_DATA_HOME:-$HOME/.local/share}/issue-277/diffview.nvim` when that folder is absent, so the first run needs the network. The clone is not pinned, and the script keeps it after exit. Its Neovim runs with `NVIM_APPNAME=issue-277`, so it writes its ShaDa file and logs under `${XDG_STATE_HOME:-$HOME/.local/state}/issue-277/`, and diffview.nvim writes `${XDG_CACHE_HOME:-$HOME/.cache}/issue-277/diffview.log`. The script ends each `nvim --remote-expr` call after 10 s and each `websocat` call after 15 s with `perl -e 'alarm N; exec ...'`, and it sleeps with `perl`, because macOS has no `timeout` [repro_issue_277.sh:DIFFVIEW_DIR, rexpr, mcp_call]. - When `ISSUE277_ARTIFACTS` is unset, the screen snapshots go to `artifacts/` in the work dir, and cleanup removes them. A run that reaches its summary (exit 0 or 1) first copies them with `cp -R` to `${TMPDIR:-/tmp}/issue277-artifacts-latest`. When that folder already exists, the copy lands in `issue277-artifacts-latest/artifacts/`, and the files at the top level stay from the first run. A setup failure (exit 2) copies nothing. When `ISSUE277_ARTIFACTS` is set, the script creates that folder before its tool check and keeps it [repro_issue_277.sh:ARTIFACTS, snap].