From c812dee1625099a137dbead3d6f241eb4ee748a3 Mon Sep 17 00:00:00 2001 From: quolu <226230081+quolu@users.noreply.github.com> Date: Sat, 3 Oct 2026 10:37:06 +0000 Subject: [PATCH 1/2] Fix runtime-error report schedule templates for service-manager PATH The launchd and systemd templates started the CLI through its env node shebang. launchd's default PATH has no Homebrew or nvm directory, so the agent exited 127 on macOS; a systemd user unit resolved a different system Node. Start Node by absolute path with separate node and script placeholders, and add a Windows Task Scheduler installer that registers the hourly task with a hidden console window. Co-Authored-By: Claude Opus 5.5 --- README.ja.md | 9 ++++- README.md | 11 ++++-- .../dev.spotter.runtime-errors.plist | 3 +- .../spotter-runtime-errors.service | 2 +- .../windows/install-runtime-errors-task.ps1 | 11 ++++++ .../windows/spotter-runtime-errors.ps1 | 4 ++ test/runtime-reporting-ops.test.mjs | 38 +++++++++++++++++++ 7 files changed, 71 insertions(+), 7 deletions(-) create mode 100644 ops/runtime-reporting/windows/install-runtime-errors-task.ps1 create mode 100644 ops/runtime-reporting/windows/spotter-runtime-errors.ps1 create mode 100644 test/runtime-reporting-ops.test.mjs diff --git a/README.ja.md b/README.ja.md index 117f25a..2cfd8a7 100644 --- a/README.ja.md +++ b/README.ja.md @@ -314,8 +314,13 @@ BugHub担当が別途、端末ごとの合鍵を`~/.config/bughub/product-creden `spotter runtime-errors report`を実行し、継続時はlaunchd、systemd user timer、Windows Task Schedulerで 同じコマンドを1時間に1回起動します。未受領だけを送信し、署名を検証した200応答だけでackします。 配布物の`ops/runtime-reporting/`にlaunchdとsystemd user timerの雛形があります。 -`REPLACE_WITH_ABSOLUTE_SPOTTER_PATH`を導入済みCLIの絶対pathへ替えてから登録します。 -Windowsでは同じ利用者のTask Schedulerへ`spotter.cmd runtime-errors report`を1時間ごとに登録します。 +登録前に、`REPLACE_WITH_ABSOLUTE_NODE_PATH`をSpotterを導入したNode.jsの絶対path(`command -v node`)へ、 +`REPLACE_WITH_ABSOLUTE_SPOTTER_SCRIPT_PATH`を導入済みの`claude-spotter/bin/spotter.mjs`の絶対path +(`realpath "$(command -v spotter)"`)へ替えます。service managerはlogin shellのPATHを読みません。 +launchdの既定PATHにはHomebrewやnvmのdirectoryが無く、CLIの`env node` shebangは起動に失敗します。 +systemd user unitは別のsystem Nodeを選ぶことがあります。そのため雛形はNodeを直接起動します。 +Windowsでは同じ利用者で、導入済みpackageの`ops\runtime-reporting\windows\install-runtime-errors-task.ps1`を +実行します。1時間ごとの`spotter-runtime-errors` taskを、console windowを隠して登録します。 422が返った版は再送を止め、修正版を導入すると再試行します。無効化は`reporting.enabled`を`false`にします。 401/403で拒まれた合鍵は入れ替わるまで再送を止めます。時計ずれはackせず、時刻修正後に再試行します。 diff --git a/README.md b/README.md index cfb924a..d29a69c 100644 --- a/README.md +++ b/README.md @@ -343,9 +343,14 @@ BugHub's operator supplies a separate `0600` credential at `~/.config/bughub/product-credentials/spotter.json` (Windows: `%LOCALAPPDATA%\bughub\product-credentials\spotter.json`). Run `spotter runtime-errors report` once, then schedule the same command hourly with launchd, a systemd user timer, or Windows Task Scheduler. -The package includes launchd and systemd templates in `ops/runtime-reporting/`; replace -`REPLACE_WITH_ABSOLUTE_SPOTTER_PATH` with the installed CLI path before registering them. On Windows, -register an hourly task for the installed `spotter.cmd runtime-errors report` under the same user account. +The package includes launchd and systemd templates in `ops/runtime-reporting/`. Before registering them, +replace `REPLACE_WITH_ABSOLUTE_NODE_PATH` with the Node.js binary that installed Spotter (`command -v node`) +and `REPLACE_WITH_ABSOLUTE_SPOTTER_SCRIPT_PATH` with the installed `claude-spotter/bin/spotter.mjs` +(`realpath "$(command -v spotter)"`). The templates start Node directly because a service manager does not +read the login shell's PATH: launchd's default PATH has no Homebrew or nvm directory, so the CLI's +`env node` shebang fails there, and a systemd user unit may resolve a different system Node. On Windows, +run `ops\runtime-reporting\windows\install-runtime-errors-task.ps1` from the installed package as the +same user; it registers the hourly `spotter-runtime-errors` task with a hidden console window. Only unacknowledged records are sent. A signed 200 response advances the cursor; timeout, 5xx, or an unverified response leaves it pending. A 422 blocks further sends for that installed version until a corrected Spotter version is installed; the block marker is under the private `~/.spotter` state directory diff --git a/ops/runtime-reporting/dev.spotter.runtime-errors.plist b/ops/runtime-reporting/dev.spotter.runtime-errors.plist index f519cfd..f977bef 100644 --- a/ops/runtime-reporting/dev.spotter.runtime-errors.plist +++ b/ops/runtime-reporting/dev.spotter.runtime-errors.plist @@ -3,7 +3,8 @@ Labeldev.spotter.runtime-errors ProgramArguments - REPLACE_WITH_ABSOLUTE_SPOTTER_PATH + REPLACE_WITH_ABSOLUTE_NODE_PATH + REPLACE_WITH_ABSOLUTE_SPOTTER_SCRIPT_PATH runtime-errorsreport StartInterval3600 diff --git a/ops/runtime-reporting/spotter-runtime-errors.service b/ops/runtime-reporting/spotter-runtime-errors.service index 41710b5..d89e3a9 100644 --- a/ops/runtime-reporting/spotter-runtime-errors.service +++ b/ops/runtime-reporting/spotter-runtime-errors.service @@ -3,4 +3,4 @@ Description=Send pending Spotter runtime errors to owner-LAN BugHub [Service] Type=oneshot -ExecStart=REPLACE_WITH_ABSOLUTE_SPOTTER_PATH runtime-errors report +ExecStart=REPLACE_WITH_ABSOLUTE_NODE_PATH REPLACE_WITH_ABSOLUTE_SPOTTER_SCRIPT_PATH runtime-errors report diff --git a/ops/runtime-reporting/windows/install-runtime-errors-task.ps1 b/ops/runtime-reporting/windows/install-runtime-errors-task.ps1 new file mode 100644 index 0000000..5f63565 --- /dev/null +++ b/ops/runtime-reporting/windows/install-runtime-errors-task.ps1 @@ -0,0 +1,11 @@ +$ErrorActionPreference = 'Stop' +$ops = Split-Path -Parent $MyInvocation.MyCommand.Path +$powershell = "$env:WINDIR\System32\WindowsPowerShell\v1.0\powershell.exe" +$principal = New-ScheduledTaskPrincipal -UserId $env:USERNAME -LogonType Interactive -RunLevel Limited +$settings = New-ScheduledTaskSettingsSet -StartWhenAvailable -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -ExecutionTimeLimit (New-TimeSpan -Minutes 5) +$trigger = New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes(5) -RepetitionInterval (New-TimeSpan -Hours 1) + +$action = New-ScheduledTaskAction -Execute $powershell -Argument "-NoProfile -NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -File `"$ops\spotter-runtime-errors.ps1`"" +Register-ScheduledTask -TaskName 'spotter-runtime-errors' -Action $action -Trigger $trigger -Principal $principal -Settings $settings -Description 'Send pending Spotter runtime errors to owner-LAN BugHub hourly' -Force | Out-Null + +Start-ScheduledTask -TaskName 'spotter-runtime-errors' diff --git a/ops/runtime-reporting/windows/spotter-runtime-errors.ps1 b/ops/runtime-reporting/windows/spotter-runtime-errors.ps1 new file mode 100644 index 0000000..bfe55ab --- /dev/null +++ b/ops/runtime-reporting/windows/spotter-runtime-errors.ps1 @@ -0,0 +1,4 @@ +$ErrorActionPreference = 'Stop' +$spotter = Join-Path $env:APPDATA 'npm\spotter.cmd' +& $spotter runtime-errors report +exit $LASTEXITCODE diff --git a/test/runtime-reporting-ops.test.mjs b/test/runtime-reporting-ops.test.mjs new file mode 100644 index 0000000..b8043c5 --- /dev/null +++ b/test/runtime-reporting-ops.test.mjs @@ -0,0 +1,38 @@ +import assert from 'node:assert/strict'; +import { readFile } from 'node:fs/promises'; +import test from 'node:test'; + +const read = (path) => readFile(new URL(`../${path}`, import.meta.url), 'utf8'); + +test('systemd report service starts Node by absolute path instead of the spotter env shebang', async () => { + const service = await read('ops/runtime-reporting/spotter-runtime-errors.service'); + assert.match( + service, + /^ExecStart=REPLACE_WITH_ABSOLUTE_NODE_PATH REPLACE_WITH_ABSOLUTE_SPOTTER_SCRIPT_PATH runtime-errors report$/mu, + ); +}); + +test('launchd report agent starts Node by absolute path instead of the spotter env shebang', async () => { + const plist = await read('ops/runtime-reporting/dev.spotter.runtime-errors.plist'); + const args = [...plist.matchAll(/([^<]+)<\/string>/gu)].map((match) => match[1]); + assert.deepEqual(args, [ + 'dev.spotter.runtime-errors', + 'REPLACE_WITH_ABSOLUTE_NODE_PATH', + 'REPLACE_WITH_ABSOLUTE_SPOTTER_SCRIPT_PATH', + 'runtime-errors', + 'report', + ]); +}); + +test('Windows report task keeps the user profile, runs hourly, and hides its console window', async () => { + const installer = await read('ops/runtime-reporting/windows/install-runtime-errors-task.ps1'); + const runner = await read('ops/runtime-reporting/windows/spotter-runtime-errors.ps1'); + assert.match(installer, /-UserId \$env:USERNAME -LogonType Interactive/); + assert.match(installer, /-RepetitionInterval \(New-TimeSpan -Hours 1\)/); + const actions = installer.match(/New-ScheduledTaskAction[^\r\n]+/gu) ?? []; + assert.equal(actions.length, 1); + assert.match(actions[0], /-NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass/); + assert.match(actions[0], /spotter-runtime-errors\.ps1/); + assert.match(runner, /runtime-errors report/); + assert.match(runner, /exit \$LASTEXITCODE/); +}); From 97b89bb49b909fb1ee8044769a8cb5e2b29f5747 Mon Sep 17 00:00:00 2001 From: quolu <226230081+quolu@users.noreply.github.com> Date: Sat, 3 Oct 2026 10:42:55 +0000 Subject: [PATCH 2/2] Prepare Spotter 1.9.1 schedule template fix release Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 6 ++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a7fe4e3..6362a78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,12 @@ 各節はそのversion公開時点の変更記録であり、後続versionにより置換された仕様を含む。 現行runtime契約は[`docs/00_overview.md`](https://github.com/kitepon/Spotter/blob/main/docs/00_overview.md)から辿る。 +## 1.9.1 — 2026-10-03 + +- runtime-error定期送信のlaunchd/systemd雛形を、NodeとCLI本体を絶対pathで起動する形へ修正。launchdの既定PATHにNodeが無く + `env node` shebangがexit 127で失敗する問題と、systemd user unitが導入時と異なるsystem Nodeを選ぶ問題を解消する。 +- Windows Task Schedulerへ1時間ごとの送信taskをconsole非表示で登録するinstallerを追加。 + ## 1.9.0 — 2026-10-03 - Spotter自身が端末内runtime-errorの累計をBugHubへ送るopt-in CLIを追加。署名済み受領だけでackし、既定では通信しない。 diff --git a/package-lock.json b/package-lock.json index c68a65d..e4fed75 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "claude-spotter", - "version": "1.9.0", + "version": "1.9.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "claude-spotter", - "version": "1.9.0", + "version": "1.9.1", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 266b111..b016148 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "claude-spotter", - "version": "1.9.0", + "version": "1.9.1", "description": "Audit agent running alongside Claude Code that catches missed tool calls — 気づく役と実行する役の分離", "type": "module", "bin": {