Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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し、既定では通信しない。
Expand Down
9 changes: 7 additions & 2 deletions README.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -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せず、時刻修正後に再試行します。

Expand Down
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion ops/runtime-reporting/dev.spotter.runtime-errors.plist
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
<plist version="1.0"><dict>
<key>Label</key><string>dev.spotter.runtime-errors</string>
<key>ProgramArguments</key><array>
<string>REPLACE_WITH_ABSOLUTE_SPOTTER_PATH</string>
<string>REPLACE_WITH_ABSOLUTE_NODE_PATH</string>
<string>REPLACE_WITH_ABSOLUTE_SPOTTER_SCRIPT_PATH</string>
<string>runtime-errors</string><string>report</string>
</array>
<key>StartInterval</key><integer>3600</integer>
Expand Down
2 changes: 1 addition & 1 deletion ops/runtime-reporting/spotter-runtime-errors.service
Original file line number Diff line number Diff line change
Expand Up @@ -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
11 changes: 11 additions & 0 deletions ops/runtime-reporting/windows/install-runtime-errors-task.ps1
Original file line number Diff line number Diff line change
@@ -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'
4 changes: 4 additions & 0 deletions ops/runtime-reporting/windows/spotter-runtime-errors.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
$ErrorActionPreference = 'Stop'
$spotter = Join-Path $env:APPDATA 'npm\spotter.cmd'
& $spotter runtime-errors report
exit $LASTEXITCODE
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
38 changes: 38 additions & 0 deletions test/runtime-reporting-ops.test.mjs
Original file line number Diff line number Diff line change
@@ -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>([^<]+)<\/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/);
});
Loading