Skip to content

feat(work-items): add sub-hour lease TTL via ttl_minutes - #2463

Merged
kyle-sexton merged 7 commits into
mainfrom
cursor/lease-ttl-minutes-63e1
Aug 12, 2026
Merged

kyle-sexton merged 7 commits into
mainfrom
cursor/lease-ttl-minutes-63e1

Conversation

@kyle-sexton

Copy link
Copy Markdown
Contributor

Fixes #1034

Adds optional ttl_minutes to lease records, --ttl-minutes on claim, and config.lease_ttl_minutes in the binding.

Test plan

  • lease.test.sh (includes fresh 15m case)

Related

@cursor

cursor Bot commented Aug 12, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@claude

claude Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 2m 28s —— View job


Security Review

  • Gather context (diff, changed files, CLAUDE.md)
  • Review claim.sh adapters (github, local-markdown) for injection / logic flaws
  • Review lease.sh TTL handling
  • Review binding.sh config handling
  • Review plugin.json / CHANGELOG / CONTRACT.md for permission or trust changes
  • Post findings

Reviewed the diff (git diff origin/main...HEAD) for plugins/work-items — the ttl_minutes lease-TTL feature. One logic/authorization-relevant regression found; no injection, credential-handling, or GitHub Actions issues in these files.


IMPORTANT — dropped renewed_at presence check lets a malformed/forged lease comment be treated as live
Confidence: High (reproduced locally)

lease.sh#L58-L77

The refactor that introduces wit_lease_ttl_seconds() drops a guard that existed in the pre-PR wit_lease_is_live:

# before (origin/main)
[[ -n "$renewed" && "$ttl" =~ ^[0-9]+$ ]] || return 1
renewed_epoch="$(wit_iso_to_epoch "$renewed")" || return 1

# after (this PR)
ttl_seconds="$(wit_lease_ttl_seconds "$lease")" || return 1
((ttl_seconds > 0)) || return 1
renewed_epoch="$(wit_iso_to_epoch "$renewed")" || return 1

renewed is no longer required to be non-empty before being passed to wit_iso_to_epoch. On Linux, GNU date -u -d "" does not error — it silently resolves the empty string to "today at 00:00:00Z" (verified in this sandbox: date -u -d "" '+%Y-%m-%dT%H:%M:%SZ'2026-08-12T00:00:00Z). So a lease JSON with a missing/empty renewed_at is no longer rejected as invalid; it's treated as "renewed at midnight today" and can appear live for the full TTL from that point.

This matters because lease comments on the github adapter are sourced from any issue comment matching the marker prefix, with no author filter — common.sh#L187-L191:

wit_run_gh read api --paginate "repos/$owner/$repo/issues/$number/comments?per_page=100" \
  --jq '[.[] | select(.body | startswith("` (no `renewed_at`, no `superseded_at`). Under the new code this forged lease is picked up by `wit_select_active_lease` and evaluated as **live**, which:
- blocks legitimate `claim` attempts ([`claim.sh#L97`](https://github.com/melodic-software/claude-code-plugins/blob/9fdcafbac5a5919d83a4452809aeba4ef96a47ef/plugins/work-items/tools/work-item-tracker/adapters/github/claim.sh#L97)), and
- blocks `reclaim` of the item ([`reclaim.sh#L39`](https://github.com/melodic-software/claude-code-plugins/blob/9fdcafbac5a5919d83a4452809aeba4ef96a47ef/plugins/work-items/tools/work-item-tracker/adapters/github/reclaim.sh#L39), [`reclaim.sh#L73`](https://github.com/melodic-software/claude-code-plugins/blob/9fdcafbac5a5919d83a4452809aeba4ef96a47ef/plugins/work-items/tools/work-item-tracker/adapters/github/reclaim.sh#L73)),

effectively letting a single crafted comment lock a work item indefinitely — a denial-of-service on the race-safe claim protocol that this file's own docstring says it guards. The comment-authorship trust boundary predates this PR, but the missing-`renewed_at`"treated as live" behavior is newly introduced here; the added test (`FRESH_15M`) doesn't cover a missing/empty `renewed_at`, so it went uncaught.

[Fix this →](https://claude.ai/code?q=In%20plugins%2Fwork-items%2Ftools%2Fwork-item-tracker%2Flib%2Flease.sh%2C%20the%20wit_lease_is_live%20function%20%28around%20line%2069%29%20no%20longer%20checks%20that%20%24renewed%20is%20non-empty%20before%20calling%20wit_iso_to_epoch%2C%20unlike%20the%20pre-PR%20version%20which%20had%20%60%5B%5B%20-n%20%22%24renewed%22%20%26%26%20...%20%5D%5D%20%7C%7C%20return%201%60.%20Because%20GNU%20%60date%20-u%20-d%20%22%22%60%20silently%20resolves%20to%20midnight%20today%20instead%20of%20erroring%2C%20a%20lease%20JSON%20missing%20renewed_at%20is%20now%20treated%20as%20live%20instead%20of%20invalid.%20Restore%20the%20explicit%20%5B%5B%20-n%20%22%24renewed%22%20%5D%5D%20%7C%7C%20return%201%20guard%20in%20wit_lease_is_live%20before%20calling%20wit_iso_to_epoch%2C%20and%20add%20a%20lease.test.sh%20case%20for%20a%20lease%20missing%20renewed_at%20to%20prevent%20regression.&repo=melodic-software/claude-code-plugins)

---

No other issues found. `jq --arg`/`jq -cn --arg` is used consistently for all JSON construction in `claim.sh` (both adapters), so there's no injection surface from `ttl_minutes`/`ttl` values into the lease JSON. Input validation (`[[ "$ttl_minutes" =~ ^[0-9]+$ ]]`) is applied consistently in both adapters and in `binding.sh` before the value is trusted. No changes to workflow permissions, trigger types, or secret handling in this PR (only shell/docs/manifest files changed) — supply-chain and workflow-trigger patterns are out of scope for this lane per zizmor's coverage.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9fdcafbac5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread plugins/work-items/tools/work-item-tracker/adapters/local-markdown/claim.sh Outdated
Comment thread plugins/work-items/tools/work-item-tracker/lib/binding.sh Outdated
Comment thread plugins/work-items/tools/work-item-tracker/adapters/github/claim.sh
@claude

claude Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 3m 13s —— View job


Security Review

  • Gather context (diff, changed files, CLAUDE.md)
  • Review claim.sh adapters (github, local-markdown) for injection / logic flaws
  • Review lease.sh TTL handling
  • Review binding.sh config handling
  • Review plugin.json / CHANGELOG / CONTRACT.md for permission or trust changes
  • Post findings

Reviewed git diff origin/main...HEAD for the 7 files changed in this PR (plugins/work-items/**, the ttl_minutes sub-hour lease TTL feature). One authorization/logic-relevant finding; no injection, credential-handling, or GitHub Actions permission issues in these files.


IMPORTANT — missing renewed_at is no longer rejected, so a lease with no (or empty) renewed_at is treated as live
Severity: IMPORTANT · Confidence: High (reproduced)

lease.sh#L68-L77

The refactor that extracts wit_lease_ttl_seconds() for the new minutes math drops the pre-PR non-empty check on renewed:

# origin/main
[[ -n "$renewed" && "$ttl" =~ ^[0-9]+$ ]] || return 1
renewed_epoch="$(wit_iso_to_epoch "$renewed")" || return 1

# this PR
ttl_seconds="$(wit_lease_ttl_seconds "$lease")" || return 1
((ttl_seconds > 0)) || return 1
renewed_epoch="$(wit_iso_to_epoch "$renewed")" || return 1

renewed is now passed to wit_iso_to_epoch without ever being checked for -n. On Linux, GNU date -u -d "" does not error — it silently resolves the empty string to midnight UTC today (reproduced in this sandbox: date -u -d "" '+%s' succeeds). So a lease JSON with a missing or empty renewed_at is no longer rejected as malformed; wit_lease_is_live instead treats it as "renewed at 00:00 UTC today" and it reads as live for up to ttl_hours/ttl_minutes past that point.

For the github adapter, lease comments are sourced from any issue comment matching the marker prefix, with no author filter (common.sh#L189-190):

wit_run_gh read api --paginate ".../comments?per_page=100" \
  --jq '[.[] | select(.body | startswith("` (no `renewed_at`, no `superseded_at`). `wit_select_active_lease` picks this up and `wit_lease_is_live` now evaluates it as live, which:
- blocks legitimate `claim` attempts ([`claim.sh#L97`](https://github.com/melodic-software/claude-code-plugins/blob/081adac325ab05535d4f8627cad9d4085550c342/plugins/work-items/tools/work-item-tracker/adapters/github/claim.sh#L97)), and
- blocks `reclaim` of the item (same lease-liveness check is used there).

This lets a single crafted comment lock a work item indefinitely — a denial-of-service on the race-safe claim protocol this file's docstring exists to guard. The comment-authorship trust boundary itself predates this PR, but the "missing `renewed_at` → treated as live" behavior is newly introduced here (the pre-PR code correctly rejected it via `[[ -n "$renewed" ]]`). The added `FRESH_15M` test case doesn't cover a missing/empty `renewed_at`, so this regression isn't caught by `lease.test.sh`.

[Fix this →](https://claude.ai/code?q=In%20plugins%2Fwork-items%2Ftools%2Fwork-item-tracker%2Flib%2Flease.sh%2C%20the%20wit_lease_is_live%20function%20%28around%20line%2069%29%20no%20longer%20checks%20that%20%24renewed%20is%20non-empty%20before%20calling%20wit_iso_to_epoch%2C%20unlike%20the%20pre-PR%20version%20which%20had%20%60%5B%5B%20-n%20%22%24renewed%22%20%26%26%20...%20%5D%5D%20%7C%7C%20return%201%60.%20Because%20GNU%20%60date%20-u%20-d%20%22%22%60%20silently%20resolves%20to%20midnight%20today%20instead%20of%20erroring%2C%20a%20lease%20JSON%20missing%20renewed_at%20is%20now%20treated%20as%20live%20instead%20of%20invalid.%20Restore%20the%20explicit%20%5B%5B%20-n%20%22%24renewed%22%20%5D%5D%20%7C%7C%20return%201%20guard%20in%20wit_lease_is_live%20before%20calling%20wit_iso_to_epoch%2C%20and%20add%20a%20lease.test.sh%20case%20for%20a%20lease%20missing%20renewed_at%20to%20prevent%20regression.&repo=melodic-software/claude-code-plugins)

---

No other security issues found. `jq --arg`/`jq -cn --arg` is used consistently for all JSON construction in `claim.sh` (both adapters), so there's no injection surface from `ttl_minutes`/`ttl` values into lease JSON. `[[ "$ttl_minutes" =~ ^[0-9]+$ ]]` input validation is applied consistently in both adapters and in `binding.sh` before the value is trusted. The `binding.sh` provider-name regex guard (`^[a-zA-Z0-9_-]+$`, preventing path traversal into `adapters/<provider>`) is unchanged by this PR. No changes to workflow permissions, trigger types, or secret handling — only shell/docs/manifest files changed in this PR — so supply-chain and workflow-trigger patterns are out of scope for this lane per zizmor's coverage.

*(Out of scope for this security lane, noted only for awareness: `plugins/work-items/CHANGELOG.md` in this PR's HEAD contains unresolved `<<<<<<<`/`=======`/`>>>>>>>` merge-conflict markers — a correctness/documentation issue, not a security one, so not detailed further here. Two functional bugs in `local-markdown/claim.sh` and `github/claim.sh` were also flagged by Codex's review on this PR — an extra `}` breaking the result-emission jq filter, and `ttl_minutes` being dropped from emitted/renewed lease records — both are correctness gaps, not authorization bypasses, since the pre-write lease check still enforces exclusivity either way.)*

@claude

claude Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 5m 5s —— View job


Code Review

  • Gather context (diff git diff 311eed6...HEAD, CLAUDE.md, REVIEW.md)
  • Review claim.sh adapters (github, local-markdown)
  • Review lease.sh TTL handling
  • Review binding.sh config handling
  • Review CONTRACT.md / CHANGELOG for consistency
  • Review test coverage (lease.test.sh)
  • Post findings

Reviewed for correctness, error handling, test coverage, and maintainability per this repo's REVIEW.md code-review-lane scope (security is left to the dedicated security lane, which already reviewed this PR). Posted 4 inline comments on changed lines; two more cross-file findings below since they involve code the diff doesn't touch.

Verified independently (not relying on the PR's own test-plan claims): reproduced the local-markdown/claim.sh jq syntax error with a minimal jq -n repro, and read plugins/work-items/CHANGELOG.md at HEAD to confirm the conflict markers are literally present in the file, not an artifact of the diff view.

Inline findings

  1. 🔴 CHANGELOG.md lines 10–29 — unresolved git merge-conflict markers (<<<<<<< HEAD / ======= / >>>>>>> f24f7a7f) committed verbatim; the file is malformed as shipped.
  2. 🔴 adapters/local-markdown/claim.sh:85 — stray trailing } makes the final result jq program a syntax error (repro'd). The lease + assignee are already written by this point, so a successful claim exits nonzero with no JSON on stdout — same defect Codex's automated review flagged as P1.
  3. 🔴 lib/binding.sh:48CONTRACT.md documents lease_ttl_minutes as 0–59, but neither binding.sh nor either adapter's --ttl-minutes validation enforces the upper bound (same gap Codex flagged as P2).
  4. 🟡 lib/binding.sh:47 — the new minutes local var is missing from the function's local declaration, so it leaks as a global into the sourcing caller, unlike every sibling variable in the same function.

Cross-file, not anchorable to a changed line

  • ttl_minutes is dropped from command output in two more places this PR didn't touch, but whose correctness this PR's own feature now depends on:
    • adapters/github/claim.sh's final result serializer (lines 127–131) only emits ttl_hours. A claim --ttl-minutes 15 call posts the correct lease comment but returns ttl_hours: 0 to the caller with no ttl_minutes field — indistinguishable from an already-dead lease.
    • Both renew-lease.sh scripts (github#L81-L84, local-markdown#L64-L67) reconstruct a fixed-shape output object that likewise omits ttl_minutes, even though the renewed lease JSON they operate on carries it.
    • Net effect: ttl_minutes only round-trips correctly through the internal lease record (GitHub comment / markdown line); every command that returns a JSON result to a caller silently drops it. Worth fixing alongside item 2 so the new field is actually consumable end-to-end, not just stored.
  • Test coverage gap: lease.test.sh's only addition (FRESH_15M) exercises wit_lease_is_live directly against lib/lease.sh — it doesn't go through either claim.sh adapter's CLI, which is why the jq syntax bug (finding 2) wasn't caught. This repo has a conformance suite (conformance/run-conformance.sh --binding local-markdown) that drives claim through the real CLI; a --ttl-minutes case there would have caught this and would also cover the output-shape gap above.

No other correctness/design issues found in the diff — the jq --arg/jq -cn --arg usage is consistent, and [[ "$ttl_minutes" =~ ^[0-9]+$ ]] input validation (modulo the missing upper bound) is applied consistently across both adapters and binding.sh.
· branch cursor/lease-ttl-minutes-63e1

@kyle-sexton
kyle-sexton force-pushed the cursor/lease-ttl-minutes-63e1 branch from 081adac to 1858fec Compare August 12, 2026 17:14
Comment thread plugins/work-items/CHANGELOG.md Outdated
Comment thread plugins/work-items/tools/work-item-tracker/adapters/local-markdown/claim.sh Outdated
Comment thread plugins/work-items/tools/work-item-tracker/lib/binding.sh Outdated
Comment thread plugins/work-items/tools/work-item-tracker/lib/binding.sh Outdated
@github-actions

github-actions Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Claude has reviewed this PR 3 times. The lane skips further automatic reviews after 5; deleting this comment resets the count.

@claude

claude Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Claude finished @kyle-sexton's task in 1m 16s —— View job


Security Review

  • Gather context (diff, changed files, CLAUDE.md)
  • Review claim.sh adapters (github, local-markdown) for injection / logic flaws
  • Review lease.sh TTL handling
  • Review binding.sh config handling
  • Review plugin.json / CHANGELOG / CONTRACT.md for permission or trust changes
  • Post findings

Reviewed git diff origin/main...HEAD for the 7 changed files in plugins/work-items/** (the ttl_minutes sub-hour lease TTL feature), at HEAD 1858fec.


IMPORTANT — missing/empty renewed_at is no longer rejected, so a forged lease with no renewed_at reads as live
Severity: IMPORTANT · Confidence: High (reproduced)

lease.sh#L68-L77

The refactor that extracts wit_lease_ttl_seconds() drops the pre-PR non-empty check on renewed:

# origin/main
[[ -n "$renewed" && "$ttl" =~ ^[0-9]+$ ]] || return 1
renewed_epoch="$(wit_iso_to_epoch "$renewed")" || return 1

# this PR
ttl_seconds="$(wit_lease_ttl_seconds "$lease")" || return 1
((ttl_seconds > 0)) || return 1
renewed_epoch="$(wit_iso_to_epoch "$renewed")" || return 1

renewed is now passed straight to wit_iso_to_epoch with no -n check. On Linux, GNU date -u -d "" does not error — it silently resolves the empty string to midnight UTC today (reproduced in this sandbox: date -u -d "" '+%Y-%m-%dT%H:%M:%SZ'2026-08-12T00:00:00Z). So a lease JSON with a missing or empty renewed_at is no longer rejected as malformed; wit_lease_is_live treats it as "renewed at 00:00 UTC today" and it reads as live for up to ttl_hours/ttl_minutes past that point.

For the github adapter, lease comments are sourced from any issue comment matching the marker prefix, with no author filter (common.sh#L187-191). A comment body of `` (no renewed_at, no `superseded_at`) is picked up by `wit_select_active_lease` and, under this PR's code, evaluates as live via the path above. That:

  • blocks legitimate claim attempts — the arbitration loop at claim.sh#L97 treats it as the winning live lease, and
  • blocks reclaim of the item (same wit_lease_is_live liveness check gates reclaim).

A single crafted comment can lock a work item indefinitely — a denial-of-service on the race-safe claim protocol this file's own docstring exists to guard. The comment-authorship trust boundary predates this PR, but the "missing renewed_at → treated as live" behavior is newly introduced here (pre-PR code correctly rejected it via [[ -n "$renewed" ]]). The added FRESH_15M test case in lease.test.sh doesn't cover a missing/empty renewed_at, so this regression isn't caught by the suite.

Fix this →


No other security issues found. jq --arg / jq -cn --arg is used consistently for all JSON construction in claim.sh (both adapters), so there's no injection surface from ttl_minutes values into lease JSON or GitHub comment bodies. [[ "$ttl_minutes" =~ ^[0-9]+$ ]] input validation is applied consistently in both adapters and in binding.sh before the value is trusted (rejects negative/non-numeric input, including shell-metacharacter payloads). The binding.sh provider-name regex guard (^[a-zA-Z0-9_-]+$, preventing path traversal into adapters/<provider>) is unchanged by this PR. No changes to workflow permissions, trigger types, or secret handling — only shell/docs/manifest files changed — so supply-chain and workflow-trigger patterns are out of scope for this lane per zizmor's coverage.

(Out of scope for this lane, noted only for awareness: CONTRACT.md's documented 0–59 range for ttl_minutes isn't enforced by any of the three validation sites — a spec-conformance gap, not a trust-boundary issue, since an out-of-range value just produces an oversized-but-still-correctly-computed TTL. Also unrelated to security: plugins/work-items/CHANGELOG.md still contains unresolved <<<<<<</=======/>>>>>>> merge-conflict markers at HEAD, and Codex's review flagged two functional bugs — an extra } breaking local-markdown/claim.sh's result-emission jq filter, and ttl_minutes being dropped from github/claim.sh's emitted/renewed lease records — both correctness gaps, not authorization bypasses.)

@cursor
cursor Bot force-pushed the cursor/lease-ttl-minutes-63e1 branch 2 times, most recently from eb19585 to 2bbe1c8 Compare August 12, 2026 17:48
cursoragent and others added 4 commits August 12, 2026 18:56
Fixes #1034

Adds optional ttl_minutes to lease records, --ttl-minutes on claim, and
config.lease_ttl_minutes in the binding. Expiry uses hours*3600 + minutes*60.

Co-authored-by: Kyle Sexton <kyle-sexton@users.noreply.github.com>
Co-authored-by: Kyle Sexton <kyle-sexton@users.noreply.github.com>
Co-authored-by: Kyle Sexton <kyle-sexton@users.noreply.github.com>
Co-authored-by: Kyle Sexton <kyle-sexton@users.noreply.github.com>
@cursor
cursor Bot force-pushed the cursor/lease-ttl-minutes-63e1 branch from 2bbe1c8 to 3792762 Compare August 12, 2026 18:56
@claude

claude Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Claude finished @cursor[bot]'s task in 1m 45s —— View job


Security Review

  • Gather context (diff git diff fa3f120...3792762, prior review history on this PR)
  • Confirm whether the current HEAD introduces new security-relevant surface since the last completed review
  • Post findings / skip-gate result

Skip gate: no new security-relevant surface at this HEAD.

The commits since the last completed security review of this PR's feature commit (e50ad54) are:

  • 9937521 chore: bump to 0.35.8 for changelog parity
  • 8b2143b style: fix MD012 in CHANGELOG
  • 3792762 chore: bump to 0.35.9

git diff e50ad54...3792762 touches only plugins/work-items/.claude-plugin/plugin.json (version string bump) and plugins/work-items/CHANGELOG.md (formatting/changelog text) — no shell, adapter, lease, or binding logic changed. Per this lane's skip gate, that's "no security-relevant surface after reading the diff," so no new review is warranted for this head.

Unresolved from prior reviews (unchanged, still present at this HEAD — for awareness, not re-reported as a new finding):
lib/lease.sh#L69-L77wit_lease_is_live still passes renewed_at to wit_iso_to_epoch without the pre-PR [[ -n "$renewed" ]] guard. A lease JSON with a missing/empty renewed_at (e.g. a forged GitHub comment matching the lease marker prefix, sourced with no author filter) is still treated as "renewed at midnight UTC today" rather than rejected, letting it read as live and block claim/reclaim on the item. This was flagged as IMPORTANT in three prior review passes on this PR and has not been addressed in the commits since. No action needed from this pass — flagging again would duplicate those existing comments — but it remains unresolved and worth fixing before merge:

Fix this →

No new security findings at this head. All other prior findings on this PR (CHANGELOG merge-conflict markers, the local-markdown claim.sh jq syntax error, dropped ttl_minutes in output serializers, missing local declaration, unenforced 0–59 bound) are correctness/style, not security, and are out of scope for this lane per REVIEW.md.

branch cursor/lease-ttl-minutes-63e1

@github-actions

github-actions Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Last security-reviewed head: 3bc184619cc58f80f9dfc70706d00938362d7c9d. On the next push, the relevance gate compares only the commits since this SHA; delete this comment to force a full re-review.

@claude

claude Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Claude finished @cursor[bot]'s task in 3m 32s —— View job


Code Review

  • Gather context (gh pr diff 2463, CLAUDE.md/REVIEW.md conventions, prior review threads)
  • Review claim.sh adapters (github, local-markdown)
  • Review lease.sh TTL handling (correctness portion only — the trust-boundary/liveness gap is security-lane scope and is already tracked there)
  • Review binding.sh config handling
  • Review CHANGELOG.md / CONTRACT.md for consistency
  • Review test coverage (lease.test.sh)
  • Post findings

Reviewed gh pr diff 2463 at HEAD 3792762 per this repo's REVIEW.md code-review-lane scope (security left to the dedicated security lane, which has already reviewed this PR separately). This PR has been reviewed by this lane before; all four previously-flagged findings are still present at this HEAD — the two intervening "bump" commits (9937521, 8b2143b, 3792762) didn't address any of them, and one (the CHANGELOG issue) is now worse. Posted 4 inline comments on changed lines; one more cross-file finding below since it spans files this diff didn't touch.

Inline findings (posted on the diff)

  1. 🔴 CHANGELOG.md:42 — unresolved merge-conflict markers still present, and now there's an added regression: [0.35.8] and [0.35.9] both read "Add sub-hour lease TTL via ttl_minutes" verbatim — [0.35.8]'s original "Harden triage intake…" entry was overwritten instead of preserved.
  2. 🔴 adapters/local-markdown/claim.sh:85 — stray trailing } still makes the final result jq program a syntax error (reproduced again). Claim succeeds internally but the command exits nonzero with no JSON on stdout.
  3. 🔴 lib/binding.sh:48CONTRACT.md's documented 0–59 range for lease_ttl_minutes/--ttl-minutes still isn't enforced in any of the three validation sites.
  4. 🟡 lib/binding.sh:47minutes still missing from the function's local declaration; leaks as a global into the sourcing caller.

Cross-file, not anchorable to a changed line

  • ttl_minutes still doesn't round-trip out of any command that returns a result to a caller, confirmed unchanged at this HEAD:
    • adapters/github/claim.sh#L127-L131 — final result serializer emits only ttl_hours; a claim --ttl-minutes 15 call posts the correct lease comment but returns ttl_hours: 0 with no ttl_minutes field, indistinguishable from an already-dead lease.
    • adapters/github/renew-lease.sh#L81-L84 and adapters/local-markdown/renew-lease.sh#L64-L67 — both reconstruct a fixed-shape output object that likewise omits ttl_minutes, even though the renewed lease JSON they operate on (built via . + {renewed_at: $ts}) carries it.
    • Net effect: ttl_minutes only round-trips through the internal lease record (GitHub comment / markdown line); every command that returns JSON to a caller silently drops it. These files aren't touched by this PR's diff, so a fix here is a new, separate change — flagged for awareness alongside finding 2 (both are needed for the feature to be consumable end-to-end).
  • Test coverage gap (unchanged from prior review): lease.test.sh's only addition (FRESH_15M) exercises wit_lease_is_live directly against lib/lease.sh — it doesn't go through either claim.sh adapter's CLI, which is why finding 2 (the jq syntax bug) isn't caught by the suite. The repo's conformance suite (conformance/run-conformance.sh --binding local-markdown) drives claim through the real CLI; a --ttl-minutes case there would catch both the syntax bug and the output-shape gap above.

No other correctness/design issues found. jq --arg/jq -cn --arg usage is consistent, and [[ "$ttl_minutes" =~ ^[0-9]+$ ]] validation (modulo the missing upper bound) is applied consistently across both adapters and binding.sh.
· branch cursor/lease-ttl-minutes-63e1

Comment thread plugins/work-items/CHANGELOG.md Outdated
Comment thread plugins/work-items/tools/work-item-tracker/adapters/local-markdown/claim.sh Outdated
Comment thread plugins/work-items/tools/work-item-tracker/lib/binding.sh Outdated
Comment thread plugins/work-items/tools/work-item-tracker/lib/binding.sh
Co-authored-by: Kyle Sexton <kyle-sexton@users.noreply.github.com>
@claude

claude Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Claude finished @cursor[bot]'s task in 1m 48s —— View job


Security Review

  • Check skip gate (prior review head vs current HEAD)
  • Gather diff since last reviewed head
  • Review any new security-relevant surface
  • Post findings

Skip gate: no new security-relevant surface at this HEAD.

Last completed security review on this PR covered head 3792762e1ca78b35bba9c28cf5fffb91e5112e0e. git diff 3792762...f7829b0 (current HEAD) touches only:

  • plugins/work-items/.claude-plugin/plugin.json — version bump (0.35.90.35.12) plus cosmetic normalization in userConfig description strings
  • plugins/work-items/CHANGELOG.md — changelog text only; notably the previously-flagged merge-conflict markers under [0.35.7] are now resolved and the duplicate [0.35.8]/[0.35.9] entries are disambiguated
  • Everything else in the diff between these two heads (plugins/autonomy, plugins/guardrails, plugins/rate-limit-guard, plugins/songwriting, etc.) came from the merge: forward main into cursor/lease-ttl-minutes-63e1 commit and is already on origin/main — not part of this PR

git diff 3792762...f7829b0 -- plugins/work-items/tools/work-item-tracker is emptylease.sh, claim.sh (both adapters), binding.sh, and common.sh are byte-for-byte unchanged since the last review. Per this lane's skip gate ("no security-relevant surface after reading the diff" / "this head already has a successful security review that still applies"), no new review is warranted for this head.

Unresolved from prior reviews (unchanged, still present at this HEAD — for awareness, not re-reported as a new finding):
lib/lease.sh#L69-L77wit_lease_is_live still passes renewed_at to wit_iso_to_epoch without the pre-PR [[ -n "$renewed" ]] guard. A lease JSON with a missing/empty renewed_at (e.g. a forged GitHub comment matching the lease marker prefix, sourced with no author filter) is still treated as "renewed at midnight UTC today" rather than rejected, letting it read as live and block claim/reclaim on the item. This was flagged IMPORTANT in four prior review passes on this PR and remains unaddressed. No action taken this pass to avoid duplicating those existing comments, but it should be fixed before merge:

Fix this →

No new security findings at this head. Other outstanding items on this PR (the local-markdown/claim.sh jq syntax error, dropped ttl_minutes in output serializers, missing local declaration, unenforced 0–59 bound) are correctness/style, not security, and remain out of scope for this lane per REVIEW.md.
· branch cursor/lease-ttl-minutes-63e1

Co-authored-by: Kyle Sexton <kyle-sexton@users.noreply.github.com>
@claude

claude Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Claude finished @cursor[bot]'s task in 1m 44s —— View job


Security Review

  • Check skip gate (prior review head vs current HEAD)
  • Gather diff since last reviewed head
  • Review any new security-relevant surface
  • Post findings

Skip gate: no new security-relevant surface at this HEAD.

Last completed security review on this PR covered head f7829b073d43b16c66a7b72ce7c77407084c1327. git diff f7829b0...bd96140 (current HEAD) touches only 5 files, all correctness/style fixes addressing prior code-review-lane findings — not the outstanding security finding:

  • plugins/work-items/.claude-plugin/plugin.json — version bump only (0.35.120.35.13)
  • plugins/work-items/CHANGELOG.md — changelog text only
  • adapters/github/claim.sh and lib/binding.sh — tighten ttl_minutes/minutes validation from "non-negative integer" to "0–59" (&& "$minutes" -le 59), matching CONTRACT.md's documented range. This is a more restrictive numeric bash test on an already-regex-validated value — no injection surface, no widening of trust.
  • adapters/local-markdown/claim.sh — removes the stray trailing } in the final result jq filter (the P1 syntax-error bug), and lib/binding.sh — adds the missing minutes to the local declaration.

All four are exactly the correctness/style items previously flagged by the code-review lane (jq syntax error, unenforced 0–59 bound, missing local) — none of them touch trust boundaries, input trust sources, authz, or GitHub Actions permissions. git diff f7829b0...bd96140 -- lib/lease.sh is empty. Per this lane's skip gate ("no security-relevant surface after reading the diff"), no new review is warranted for this head.

Unresolved from prior reviews (unchanged, still present at this HEAD — for awareness, not re-reported as a new finding):

lib/lease.sh#L69-L77wit_lease_is_live still passes renewed_at to wit_iso_to_epoch without the pre-PR [[ -n "$renewed" ]] guard. Re-verified in this sandbox: date -u -d "" '+%Y-%m-%dT%H:%M:%SZ'2026-08-12T00:00:00Z (no error). A lease JSON with a missing/empty renewed_at — e.g. a forged GitHub issue comment matching the lease marker prefix, sourced with no author filter at adapters/github/common.sh#L187-L191 — is still treated as "renewed at midnight UTC today" rather than rejected, so it reads as live and blocks claim/reclaim on the item via claim.sh#L97. This was flagged IMPORTANT in five prior review passes on this PR and remains unaddressed by the latest commit (bd96140), which fixed the code-review lane's findings but not this one.

Fix this →

No new security findings at this head. All other items previously flagged on this PR remain correctness/style, out of scope for this lane per REVIEW.md.
· branch cursor/lease-ttl-minutes-63e1

Co-authored-by: Kyle Sexton <kyle-sexton@users.noreply.github.com>
@claude

claude Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Claude finished @cursor[bot]'s task in 1m 2s —— View job


Security Review

Skip gate: no new security-relevant surface at this HEAD.

Last completed security review on this PR covered head bd9614086503a3dffdaa8dc95b7886771a36a212. git diff bd96140...3bc1846 (current HEAD) touches only two lines total, both in already-reviewed files:

Both changes replace an en-dash () with an ASCII hyphen (-) inside the --ttl-minutes must be 0–59 ... usage-error string. The validation logic itself ([[ "$ttl_minutes" =~ ^[0-9]+$ && "$ttl_minutes" -le 59 ]]) is byte-for-byte unchanged — this is a character substitution in a human-readable error message only. No trust-boundary, input-validation, or injection surface change. git diff bd96140...3bc1846 -- lib/lease.sh is empty. Per this lane's skip gate ("no security-relevant surface after reading the diff"), no new review is warranted for this head.

Unresolved from prior reviews (unchanged, still present at this HEAD — for awareness, not re-reported as a new finding):

lib/lease.sh#L69-L76wit_lease_is_live still passes renewed_at to wit_iso_to_epoch without the pre-PR [[ -n "$renewed" ]] guard. Re-verified previously in this sandbox: GNU date -u -d "" silently resolves to midnight UTC of the current day rather than erroring. A lease JSON with a missing/empty renewed_at — e.g. a forged GitHub issue comment matching the lease marker prefix, sourced with no author filter at adapters/github/common.sh#L187-L191 — is still treated as "renewed at midnight UTC today" rather than rejected, so it reads as live and blocks claim/reclaim on the item via adapters/github/claim.sh#L97. This has been flagged IMPORTANT in six prior review passes on this PR and remains unaddressed. Suggested fix (unchanged from earlier passes):

Fix this →

No new security findings at this head. All other previously flagged items on this PR (CHANGELOG merge-conflict markers, local-markdown/claim.sh jq syntax error, dropped ttl_minutes in output serializers, missing local declaration) are correctness/style and out of scope for this lane per REVIEW.md; the code-review lane already tracks those and the latest commits show most were resolved.

branch cursor/lease-ttl-minutes-63e1

@kyle-sexton
kyle-sexton merged commit 9133850 into main Aug 12, 2026
38 checks passed
@kyle-sexton
kyle-sexton deleted the cursor/lease-ttl-minutes-63e1 branch August 12, 2026 20:56
@claude

claude Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Claude finished @cursor[bot]'s task in 49s —— View job


Code Review

  • Check skip gate

Skip gate: PR is merged (not open).

This PR (#2463) is in MERGED state — the local checkout's HEAD (9133850) is the merge commit onto main, confirming it landed. Per this lane's skip gate (plugins/review/skills/code-review/SKILL.md, "PR is closed or not open" → stop early), no new review is performed and no inline comments are posted for this invocation.

For reference, this PR received extensive prior review from both the code-review and security lanes across its lifecycle, including an unresolved security finding (missing renewed_at non-empty check in wit_lease_is_live, lib/lease.sh) and several code-review findings (unresolved CHANGELOG.md merge-conflict markers, a jq syntax error in local-markdown/claim.sh, ttl_minutes not round-tripping through result serializers) — see the earlier comments on this thread. Those were the state of the code as of the last reviewed head before merge; since the PR is now closed, no further action is taken by this lane.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

work-item-tracker: lease TTL is integer-hours only — sub-hour granularity needed for high-cadence consumers

2 participants