backlog
v0.3.4v0.3.3: v0.3.2 の記述にあった事実誤認を同じ箇所で訂正した — 孤児化した settings.json のエントリを「手書き」と書いていたが、実際は `backlog install` 自身の出力である (`src/install.rs` の `binary_path()` → `std::env::current_exe()`、`command_group(..., 5)` → 稼働エントリの timeout 5)。出自が重要なのは機構を名指すからで、絶対パスを user settings へ書き込む plugin subcommand はそのバイナリが動くたびに必ず腐り、誰かが気をつけることでは直らない (CLAUDE.md 第6節)。散文のみ、挙動は無変更。 v0.3.2: SessionStart hook の宣言を plugin 内 (`hooks/hooks.json` → `${CLAUDE_PLUGIN_ROOT}/bin/backlog session-start`) へ移した。この plugin は最初から `src/hooks/session_start.rs` を持ち、この description 自身も「a SessionStart hook that surfaces pending work at session open」と書いていたが、`hooks/hooks.json` は一度も存在しなかった (`git log -- crates/backlog/hooks` は空)。唯一の配線はユーザの `~/.claude/settings.json` にある絶対パス `~/.cargo/bin/backlog session-start` だった。これは手書きではなく `backlog install` 自身の出力である: `src/install.rs` の `binary_path()` が `std::env::current_exe()` を settings.json へ焼き込み、`command_group(..., 5)` が timeout 5 を書く — 稼働中の settings.json の当該エントリと完全に一致する。つまり **plugin が自分で、必ず腐る配線を書いていた**。そのバイナリが 2026-08-20 に `backlog.bak-stale-20260820` へリネームされて以降、hook は毎セッション exit 127 (No such file or directory) で死んでいた。SessionStart hook は exit code も stderr も agent に届かないので、これは red ではなく **dark** になり、「キューが空」と「hook が走っていない」が下流で区別不能になっていた (CLAUDE.md 第1節/第3節)。実測 2026-08-26 (測定点 58c779af): 復旧後の初回実行で期限切れ deferred タスク 24 件が一括再キューされた — `requeue_expired` も同じ期間止まっていた。timeout は旧 settings.json の 5s ではなく 10s にした: tasks-file lock の blocking-acquire は実測 8s の budget を持つので (v0.2.27 参照)、5s では lock 待ちの正常系を途中で殺しうる。新規テスト `tests/session_start_hook_wired.rs` が「宣言が存在すること」と「`${CLAUDE_PLUGIN_ROOT}` 経由で解決され `.cargo/bin` を指さないこと」を pin する (追加前に RED を観測済み)。 **同じ commit で `store::requeue_expired` の terminal-status バグも直した**: hook を復旧させた初回実行が、その 24 件のうち 8 件の `done` と 6 件の `cancelled` を `pending` へ戻したので発覚した (`100af807` — CLAUDE.md が `d30d9b00` で完了と引用している項目 — を含む)。`defer_until` はこの関数でしか消されず terminal 遷移時には残るため、一度 defer して後に完了/中止したタスクは過去の `defer_until` を永久に持ち、requeue 分岐が status を見ずに `pending` を強制していた (`store::requeue_expired`。ガードは `crates/backlog/src/store.rs:1151` の `let terminal = ...`)。`done`/`cancelled` は完全に据え置く (stale な field は `next`/`is_pending` が既に除外するので無害。正規化もしないので完了時刻を再スタンプしない)。`failed` は terminal ではない — `fail --defer` の retry-later 経路であり、期限切れで `pending` に戻るのが仕様 (`requeue_expired_restores_pending`)。修正後の実測は同じ store に対して 24 件 → **10 件** (`failed` 8 + stale `claimed` 2 = 正当な分だけ)。新規テスト `requeue_expired_leaves_done_and_cancelled_terminal` が RED→GREEN で pin する (`failed`/`pending` を anti-vacuity control として同居させ、「何も requeue しない」実装で通らないようにした)。 v0.3.1: v0.3.0 が引用した実測値に測定点を付け、再測定コマンドを同梱した。再測定したら store は既に動いていた(ラベル別 pending/failed が `bb046648` で 258/66/5、`89feaddb` で 265/70/5。どちらも 2026-08-20)— CLAUDE.md の測定値ルールが防ごうとしている腐敗そのものなので、片方で上書きせず両方を rev 付きで併記した。doc とコメントのみ、挙動は変えていない。 v0.3.0: キューの見える範囲・書ける範囲を「今いる project の中」だけに揃えた (backlog 81dfaae4 / 7d8ab7fe)。repo store (`<repo root>/.backlog/tasks.toml`) は tracked file で、構造上その repo のタスクしか持たないのに、read は**書いた checkout の絶対パス**でも行を絞り込んでいた。結果、1 つの repo のキューが**マシンごとに分裂**した (実測 2026-08-20、本 repo の store: pending 258 件が macOS の checkout パス、66 件が WSL のパスでラベルされ、互いに見えなかった)。repo store では行フィルタを撤去し (ファイル自体がスコープ)、`--project` は「どの store のことを言っているか」の **assertion** になった: この repo を指すなら何も変わらず、別 repo を指すのは理由付きの非0終了で、絞り込まれた一覧は返さない (「この store は別 project だ」と「この project には積まれていない」を同じ表示に写さない。CLAUDE.md 第3節)。write 側も同じ assertion を持つ (別 repo のラベルで書かれた行は、この repo の作業として一覧に出てしまうため)。ただし**証明された不一致だけを拒否**し、identity を確認できなかった場合は従来どおり起票して不確実性をデータとして持たせる (`project_unresolved = true` → `[project unresolved: ...]` と表示)。add を止めることは起票そのものを失うことなので、そこは緩めない。後半: repo root が上に無い cwd から cross-project な `~/.backlog` へのフォールバックを廃止した。これは tempdir で走ったプロセスが fixture を本物のキューへ書き込んだ経路 (specforge の spec-ratify タスクが `project="/tmp/.tmpsYSvwG"` で発見された) であり、そういう cwd からの read が別 project の作業で答えられていた経路でもある。`config::StoreLocation` が「ここには project store が無い」を**表現可能**にし、`add`/`list`/`next` は理由と `store_dir` の逃げ道を示して拒否する。`divergence::check` は 2 つの store のスコープを別々に受け取るようになった: reader がもう適用しないフィルタで resolved store を数えると、実在の作業を載せた一覧に対して `Undetermined` (非0・stdout 空) が発火しうる — それはこのモジュールが防ぐために存在する偽の空そのもの。新規の契約テスト 6 本 + control 2 本 (`tests/project_scope.rs`)。旧フィルタ・旧フォールバックを固定していた既存 11 テストは、どちらへ倒したかと理由をその場に書いて re-pin した。 v0.2.20: 「キューが空」と「別のストアを読んでいる」を区別できるようにした (backlog 5ba13c3e 完了条件2)。キューが per-repo ストアへ移行した際に旧 `~/.backlog/tasks.toml` は移行されず、移行前のチェックアウトでは `no tasks` / `[]` が返っていた — 中身は 490 件 (p0 3件を含む) 残っていたので、これは「観測できなかった」を「何も無い」として返す fail-open だった (CLAUDE.md 第3節)。新 `divergence` モジュールが三値で答える: 旧ストアが**存在しない**マシン (新規クローン) は完全に沈黙 (Absent は本物の観測)、旧ストアが**読めない**場合は「空」と結論しない (Unreadable ≠ Absent)、解決されたストアがこの project の作業を 1 件も持たないのに旧ストアが持つ場合は `list`/`next` が空を返さず非0で終了して stderr に理由を出す (`autoflow::backlog::find_open` がこれを `Determination::Undetermined` として読み、Stop を可視的に block する)。解決されたストア側に作業がある場合は exit 0 のまま stderr で「この一覧は不完全」と警告する — ここで非0にすると非0を「none」に写す消費者 (`overwatch` の `shell_soft` → `(none)`、condukt SKILL の `2>/dev/null || true`) から実在の項目が消え、直そうとしている偽の空をこちらが作ってしまうため。SessionStart hook は exit code も stderr も agent に届かないので additionalContext に注記を注入する。v0.2.10: store.rs に PROBE_PATH_ENV_LOCK の相互排他性自体を決定論的に証明するテスト (probe_path_env_lock_actually_serializes_concurrent_holders) を追加。v0.2.9: store.rs のテストに PROBE_PATH_ENV_LOCK を追加し、real-git-init テストと PATH-hijack テストの並列実行レースを解消。 v0.2.8: `git_remote_origin_url` now bounds its `git config --get remote.origin.url` invocation with a 5s timeout (`harness_core::boundary::run_with_timeout`) instead of spawning it unbounded via a raw `Command::output()` — a hung `git` (corrupt/locked index, wedged credential helper, a stuck fsmonitor/hook subprocess) previously could block the `Add` command's caller indefinitely; a timeout (like any other git failure) now fails soft to an empty remote URL, same as today. v0.2.7: `gh_probe` now bounds every `gh` invocation with a 20s timeout (`harness_core::boundary::run_with_timeout`) instead of spawning it unbounded — `add_with_weight_and_github_push` calls `gh_probe` from inside the tasks-file lock's critical section, so a hung `gh` (e.g. stuck on an interactive network-auth prompt) previously could hold that lock indefinitely and block every other session's `backlog add`/`next --claim`; a timeout (like gh-absent) now fails soft to local-only. v0.2.6: `is_github_remote` now compares the parsed host exactly against github.com instead of a substring match, so lookalike hosts (github.company.internal, github.com.evil.example) or a github.com occurrence in the path/query of a different host no longer false-positive as a GitHub remote. v0.2.5: `backlog add` now wires the fail-soft one-way GitHub-issue push end to end — `store::add_with_weight_and_github_push` (an injectable-runner variant of add_with_weight) calls `github::decide_issue_create` inside the same tasks-file-lock critical section and, on a real `gh issue create` success, parses the issue number (`github::parse_issue_number`) and persists issue_number/issue_url on the new Task; the CLI's `Add` handler resolves the real git remote URL and spawns real `gh` via injected closures (mirroring condukt::pr), while a non-GitHub remote, absent gh, or a failed gh invocation all fail-soft to a local-only add exactly as before. v0.2.4: Task gains issue_number/issue_url (Option, serde default) fields to hold a linked GitHub issue reference; backward-compatible with existing tasks.toml records. v0.2.3: adds a GitHub issue-create helper module (github.rs). v0.2.2: `backlog list`'s default scope is now the cwd-resolved project (matching what `add --project "$PWD"` would have stored), not cross-project; pass `--all` to keep the old cross-project default (an explicit `--project` still wins over both). v0.1.12: fixes a real lost-update race in add_with_weight/next_claim under heavy concurrent contention (proven by a previously-red stress test, `add_and_claim_no_lost_update_under_heavy_contention`, now green). Root cause: the tasks-file lock's blocking-acquire retry budget (TASKS_LOCK_MAX_ATTEMPTS × TASKS_LOCK_SLEEP, ~2s) was sized for exactly one legitimate holder's worst-case critical section (up to IS_CLAIMED_TIMEOUT=300ms), not for N concurrent holders queuing serially — under 20-way contention a waiter could queue behind several 300ms holds in a row (~6s worst case), exceed the old 2s budget, and degrade to an unprotected best-effort operation (the fail-soft path), causing genuine lost updates. Budget raised to ~8s (comfortably above the realistic worst-case queue, comfortably below the 10s stale-reap window) so legitimate contention no longer forces the unprotected fallback. v0.1.11: adds a regression test (`crates/backlog/tests/lock_heartbeat_ttl.rs`) proving the v0.1.10 heartbeat_at+TTL fix actually works without waiting the real 30-minute TTL — rewrites a held lock's on-disk `heartbeat_at` back past `LOCK_STALE_TTL_SECS` to simulate elapsed time, then asserts `backlog lock heartbeat` refreshes it (a second session's acquire is blocked) and, as a control, that an un-heartbeated stale lock IS reaped and stolen by a different session. v0.1.10: fixed backlog::lock's cross-session mutual exclusion, which offered essentially zero real protection — staleness was judged by `pid_alive(existing.pid)`, but the recorded pid is always the one-shot `backlog` CLI invocation's own OS pid, dead again the instant that command returns, regardless of whether the holding session is still working. Verified empirically: a lock acquired one second earlier was already reported stale and silently stolen by a second session. Staleness is now judged by a `heartbeat_at` timestamp + 30-minute TTL (matching condukt's task-claim registry and overwatch's lease registry), with a new `backlog lock heartbeat --session-id <id>` subcommand to keep a long session's hold alive. `pid` is retained on-disk for observability only. Cross-project task queue with cycle-type tags for Claude Code: a /backlog skill (thin alias to /flow for driver loop), a SessionStart hook that surfaces pending work at session open, and a backlog binary with list/next/done/fail/lock subcommands. The lock→pick→/condukt→done driver loop is consolidated in /flow. Subscription-native (skill + hook + bundled Rust binary, no API key).
By yukineko0 GitHub starsUpdated yesterday
Directory evidence
- Runtimes
- Claude Code
- Parsed components
- 1 skill or MCP entry
- Source updated
- Sep 23, 2026
- Manifest status
- Canonical path parsed
The directory validates manifest shape and source location. It does not execute the plugin or provide a security endorsement. Review the indexing methodology →
Install backlog for Claude Code
claude plugin marketplace add IchenDEV/agent-plugin-mkt
claude plugin marketplace update agent-plugin-marketplace
claude plugin install backlog-4@agent-plugin-marketplacePaste and run these commands in a terminal with Claude Code. They add and refresh the PluginsMP catalog, then install this plugin.
The installer fetches third-party code from the source repository shown on this page. This directory validates manifest structure and source location, but does not perform a security audit; review the manifest, components, and source before installing.
Get the source manually
git clone https://github.com/yukineko/claude-harnessesClone the source repository, then follow its setup instructions to add the plugin to a compatible client. The plugin root is crates/backlog/.
Plugin files
├── .claude-plugin/plugin.json└── skills/backlog/SKILL.md
Included Skills1
クロスプロジェクト backlog の queue+state を操作するコマンド群へのショートカット。ループ driver は /flow に統合された。
Plugin manifests1
{
"name": "backlog",
"version": "0.3.4",
"description": "v0.3.3: v0.3.2 の記述にあった事実誤認を同じ箇所で訂正した — 孤児化した settings.json のエントリを「手書き」と書いていたが、実際は `backlog install` 自身の出力である (`src/install.rs` の `binary_path()` → `std::env::current_exe()`、`command_group(..., 5)` → 稼働エントリの timeout 5)。出自が重要なのは機構を名指すからで、絶対パスを user settings へ書き込む plugin subcommand はそのバイナリが動くたびに必ず腐り、誰かが気をつけることでは直らない (CLAUDE.md 第6節)。散文のみ、挙動は無変更。 v0.3.2: SessionStart hook の宣言を plugin 内 (`hooks/hooks.json` → `${CLAUDE_PLUGIN_ROOT}/bin/backlog session-start`) へ移した。この plugin は最初から `src/hooks/session_start.rs` を持ち、この description 自身も「a SessionStart hook that surfaces pending work at session open」と書いていたが、`hooks/hooks.json` は一度も存在しなかった (`git log -- crates/backlog/hooks` は空)。唯一の配線はユーザの `~/.claude/settings.json` にある絶対パス `~/.cargo/bin/backlog session-start` だった。これは手書きではなく `backlog install` 自身の出力である: `src/install.rs` の `binary_path()` が `std::env::current_exe()` を settings.json へ焼き込み、`command_group(..., 5)` が timeout 5 を書く — 稼働中の settings.json の当該エントリと完全に一致する。つまり **plugin が自分で、必ず腐る配線を書いていた**。そのバイナリが 2026-08-20 に `backlog.bak-stale-20260820` へリネームされて以降、hook は毎セッション exit 127 (No such file or directory) で死んでいた。SessionStart hook は exit code も stderr も agent に届かないので、これは red ではなく **dark** になり、「キューが空」と「hook が走っていない」が下流で区別不能になっていた (CLAUDE.md 第1節/第3節)。実測 2026-08-26 (測定点 58c779af): 復旧後の初回実行で期限切れ deferred タスク 24 件が一括再キューされた — `requeue_expired` も同じ期間止まっていた。timeout は旧 settings.json の 5s ではなく 10s にした: tasks-file lock の blocking-acquire は実測 8s の budget を持つので (v0.2.27 参照)、5s では lock 待ちの正常系を途中で殺しうる。新規テスト `tests/session_start_hook_wired.rs` が「宣言が存在すること」と「`${CLAUDE_PLUGIN_ROOT}` 経由で解決され `.cargo/bin` を指さないこと」を pin する (追加前に RED を観測済み)。 **同じ commit で `store::requeue_expired` の terminal-status バグも直した**: hook を復旧させた初回実行が、その 24 件のうち 8 件の `done` と 6 件の `cancelled` を `pending` へ戻したので発覚した (`100af807` — CLAUDE.md が `d30d9b00` で完了と引用している項目 — を含む)。`defer_until` はこの関数でしか消されず terminal 遷移時には残るため、一度 defer して後に完了/中止したタスクは過去の `defer_until` を永久に持ち、requeue 分岐が status を見ずに `pending` を強制していた (`store::requeue_expired`。ガードは `crates/backlog/src/store.rs:1151` の `let terminal = ...`)。`done`/`cancelled` は完全に据え置く (stale な field は `next`/`is_pending` が既に除外するので無害。正規化もしないので完了時刻を再スタンプしない)。`failed` は terminal ではない — `fail --defer` の retry-later 経路であり、期限切れで `pending` に戻るのが仕様 (`requeue_expired_restores_pending`)。修正後の実測は同じ store に対して 24 件 → **10 件** (`failed` 8 + stale `claimed` 2 = 正当な分だけ)。新規テスト `requeue_expired_leaves_done_and_cancelled_terminal` が RED→GREEN で pin する (`failed`/`pending` を anti-vacuity control として同居させ、「何も requeue しない」実装で通らないようにした)。 v0.3.1: v0.3.0 が引用した実測値に測定点を付け、再測定コマンドを同梱した。再測定したら store は既に動いていた(ラベル別 pending/failed が `bb046648` で 258/66/5、`89feaddb` で 265/70/5。どちらも 2026-08-20)— CLAUDE.md の測定値ルールが防ごうとしている腐敗そのものなので、片方で上書きせず両方を rev 付きで併記した。doc とコメントのみ、挙動は変えていない。 v0.3.0: キューの見える範囲・書ける範囲を「今いる project の中」だけに揃えた (backlog 81dfaae4 / 7d8ab7fe)。repo store (`<repo root>/.backlog/tasks.toml`) は tracked file で、構造上その repo のタスクしか持たないのに、read は**書いた checkout の絶対パス**でも行を絞り込んでいた。結果、1 つの repo のキューが**マシンごとに分裂**した (実測 2026-08-20、本 repo の store: pending 258 件が macOS の checkout パス、66 件が WSL のパスでラベルされ、互いに見えなかった)。repo store では行フィルタを撤去し (ファイル自体がスコープ)、`--project` は「どの store のことを言っているか」の **assertion** になった: この repo を指すなら何も変わらず、別 repo を指すのは理由付きの非0終了で、絞り込まれた一覧は返さない (「この store は別 project だ」と「この project には積まれていない」を同じ表示に写さない。CLAUDE.md 第3節)。write 側も同じ assertion を持つ (別 repo のラベルで書かれた行は、この repo の作業として一覧に出てしまうため)。ただし**証明された不一致だけを拒否**し、identity を確認できなかった場合は従来どおり起票して不確実性をデータとして持たせる (`project_unresolved = true` → `[project unresolved: ...]` と表示)。add を止めることは起票そのものを失うことなので、そこは緩めない。後半: repo root が上に無い cwd から cross-project な `~/.backlog` へのフォールバックを廃止した。これは tempdir で走ったプロセスが fixture を本物のキューへ書き込んだ経路 (specforge の spec-ratify タスクが `project=\"/tmp/.tmpsYSvwG\"` で発見された) であり、そういう cwd からの read が別 project の作業で答えられていた経路でもある。`config::StoreLocation` が「ここには project store が無い」を**表現可能**にし、`add`/`list`/`next` は理由と `store_dir` の逃げ道を示して拒否する。`divergence::check` は 2 つの store のスコープを別々に受け取るようになった: reader がもう適用しないフィルタで resolved store を数えると、実在の作業を載せた一覧に対して `Undetermined` (非0・stdout 空) が発火しうる — それはこのモジュールが防ぐために存在する偽の空そのもの。新規の契約テスト 6 本 + control 2 本 (`tests/project_scope.rs`)。旧フィルタ・旧フォールバックを固定していた既存 11 テストは、どちらへ倒したかと理由をその場に書いて re-pin した。 v0.2.20: 「キューが空」と「別のストアを読んでいる」を区別できるようにした (backlog 5ba13c3e 完了条件2)。キューが per-repo ストアへ移行した際に旧 `~/.backlog/tasks.toml` は移行されず、移行前のチェックアウトでは `no tasks` / `[]` が返っていた — 中身は 490 件 (p0 3件を含む) 残っていたので、これは「観測できなかった」を「何も無い」として返す fail-open だった (CLAUDE.md 第3節)。新 `divergence` モジュールが三値で答える: 旧ストアが**存在しない**マシン (新規クローン) は完全に沈黙 (Absent は本物の観測)、旧ストアが**読めない**場合は「空」と結論しない (Unreadable ≠ Absent)、解決されたストアがこの project の作業を 1 件も持たないのに旧ストアが持つ場合は `list`/`next` が空を返さず非0で終了して stderr に理由を出す (`autoflow::backlog::find_open` がこれを `Determination::Undetermined` として読み、Stop を可視的に block する)。解決されたストア側に作業がある場合は exit 0 のまま stderr で「この一覧は不完全」と警告する — ここで非0にすると非0を「none」に写す消費者 (`overwatch` の `shell_soft` → `(none)`、condukt SKILL の `2>/dev/null || true`) から実在の項目が消え、直そうとしている偽の空をこちらが作ってしまうため。SessionStart hook は exit code も stderr も agent に届かないので additionalContext に注記を注入する。v0.2.10: store.rs に PROBE_PATH_ENV_LOCK の相互排他性自体を決定論的に証明するテスト (probe_path_env_lock_actually_serializes_concurrent_holders) を追加。v0.2.9: store.rs のテストに PROBE_PATH_ENV_LOCK を追加し、real-git-init テストと PATH-hijack テストの並列実行レースを解消。 v0.2.8: `git_remote_origin_url` now bounds its `git config --get remote.origin.url` invocation with a 5s timeout (`harness_core::boundary::run_with_timeout`) instead of spawning it unbounded via a raw `Command::output()` — a hung `git` (corrupt/locked index, wedged credential helper, a stuck fsmonitor/hook subprocess) previously could block the `Add` command's caller indefinitely; a timeout (like any other git failure) now fails soft to an empty remote URL, same as today. v0.2.7: `gh_probe` now bounds every `gh` invocation with a 20s timeout (`harness_core::boundary::run_with_timeout`) instead of spawning it unbounded — `add_with_weight_and_github_push` calls `gh_probe` from inside the tasks-file lock's critical section, so a hung `gh` (e.g. stuck on an interactive network-auth prompt) previously could hold that lock indefinitely and block every other session's `backlog add`/`next --claim`; a timeout (like gh-absent) now fails soft to local-only. v0.2.6: `is_github_remote` now compares the parsed host exactly against github.com instead of a substring match, so lookalike hosts (github.company.internal, github.com.evil.example) or a github.com occurrence in the path/query of a different host no longer false-positive as a GitHub remote. v0.2.5: `backlog add` now wires the fail-soft one-way GitHub-issue push end to end — `store::add_with_weight_and_github_push` (an injectable-runner variant of add_with_weight) calls `github::decide_issue_create` inside the same tasks-file-lock critical section and, on a real `gh issue create` success, parses the issue number (`github::parse_issue_number`) and persists issue_number/issue_url on the new Task; the CLI's `Add` handler resolves the real git remote URL and spawns real `gh` via injected closures (mirroring condukt::pr), while a non-GitHub remote, absent gh, or a failed gh invocation all fail-soft to a local-only add exactly as before. v0.2.4: Task gains issue_number/issue_url (Option, serde default) fields to hold a linked GitHub issue reference; backward-compatible with existing tasks.toml records. v0.2.3: adds a GitHub issue-create helper module (github.rs). v0.2.2: `backlog list`'s default scope is now the cwd-resolved project (matching what `add --project \"$PWD\"` would have stored), not cross-project; pass `--all` to keep the old cross-project default (an explicit `--project` still wins over both). v0.1.12: fixes a real lost-update race in add_with_weight/next_claim under heavy concurrent contention (proven by a previously-red stress test, `add_and_claim_no_lost_update_under_heavy_contention`, now green). Root cause: the tasks-file lock's blocking-acquire retry budget (TASKS_LOCK_MAX_ATTEMPTS × TASKS_LOCK_SLEEP, ~2s) was sized for exactly one legitimate holder's worst-case critical section (up to IS_CLAIMED_TIMEOUT=300ms), not for N concurrent holders queuing serially — under 20-way contention a waiter could queue behind several 300ms holds in a row (~6s worst case), exceed the old 2s budget, and degrade to an unprotected best-effort operation (the fail-soft path), causing genuine lost updates. Budget raised to ~8s (comfortably above the realistic worst-case queue, comfortably below the 10s stale-reap window) so legitimate contention no longer forces the unprotected fallback. v0.1.11: adds a regression test (`crates/backlog/tests/lock_heartbeat_ttl.rs`) proving the v0.1.10 heartbeat_at+TTL fix actually works without waiting the real 30-minute TTL — rewrites a held lock's on-disk `heartbeat_at` back past `LOCK_STALE_TTL_SECS` to simulate elapsed time, then asserts `backlog lock heartbeat` refreshes it (a second session's acquire is blocked) and, as a control, that an un-heartbeated stale lock IS reaped and stolen by a different session. v0.1.10: fixed backlog::lock's cross-session mutual exclusion, which offered essentially zero real protection — staleness was judged by `pid_alive(existing.pid)`, but the recorded pid is always the one-shot `backlog` CLI invocation's own OS pid, dead again the instant that command returns, regardless of whether the holding session is still working. Verified empirically: a lock acquired one second earlier was already reported stale and silently stolen by a second session. Staleness is now judged by a `heartbeat_at` timestamp + 30-minute TTL (matching condukt's task-claim registry and overwatch's lease registry), with a new `backlog lock heartbeat --session-id <id>` subcommand to keep a long session's hold alive. `pid` is retained on-disk for observability only. Cross-project task queue with cycle-type tags for Claude Code: a /backlog skill (thin alias to /flow for driver loop), a SessionStart hook that surfaces pending work at session open, and a backlog binary with list/next/done/fail/lock subcommands. The lock→pick→/condukt→done driver loop is consolidated in /flow. Subscription-native (skill + hook + bundled Rust binary, no API key).",
"author": {
"name": "yukineko"
},
"keywords": [
"backlog",
"queue",
"task",
"hooks",
"rust",
"condukt"
]
}For maintainers
If you maintain this plugin, link to this source-backed listing from your README so users can review its manifest and indexed components.
[backlog on Agent Plugins Marketplace](https://pluginsmp.com/plugins/backlog-4)