Skip to content

fix(work-items): make the lease protocol runnable without GraphQL, and specify the overengineering product-code lane - #3166

Merged
kyle-sexton merged 5 commits into
mainfrom
claude/work-items-integration-h3yabz
Aug 23, 2026
Merged

kyle-sexton merged 5 commits into
mainfrom
claude/work-items-integration-h3yabz

Conversation

@kyle-sexton

Copy link
Copy Markdown
Contributor

Closes #2897

Summary

Working #2897 through /work-items:work was blocked at the very first step: the seam's claim verb could not run in a Claude Code web/remote session, so no race-safe claim could be acquired and the lane's claim-before-dispatch invariant could not be satisfied. This PR fixes the seam, then delivers #2897 with a real claim held.

Two independent changes, one per commit:

  1. work-items 0.39.15 — the lease protocol no longer depends on GraphQL, plus two correctness defects found while verifying that port end to end.
  2. overengineering 0.2.0 — the deferred product-code lane specification and the ADR deciding its shipping shape. Documentation only; no skill or behavior changes.

Fix

The lease protocol (work-items)

claim and reclaim resolved assignees through gh issue edit --add-assignee and gh issue view --json assignees. Both route through GitHub's GraphQL API, which sandboxed sessions serve only as a pinned set of PR-review operations, refusing the rest with HTTP 403. Both verbs now use the REST …/issues/<n>/assignees endpoints through shared wit_read_assignees / wit_add_assignee / wit_remove_assignee / wit_try_remove_assignee helpers.

Identity routing is unchanged: the helpers take the same read (bare gh, session identity) / write (bot wrapper) writer argument the adapter already used, so the carve-out that assigns the session user rather than the bot still holds. @me is resolved to the login explicitly because REST takes a literal login.

Verifying the port surfaced two defects that were not part of the original scope:

  • claim did not verify its own assignment landed. REST POST …/assignees returns 201 and silently drops a login that cannot be assigned, where gh issue edit --add-assignee failed loudly. Unguarded, the port would have introduced a new race: claim reporting a held lease while list-frontier still saw the item unassigned, putting two workers on one item. A dropped assignment now exits 4 before any lease comment is posted.
  • wit_lease_json matched only a body ENDING in -->. Any comment carrying the lease plus trailing text (a bot wrapper's attribution footer, a signature, a CI note) parsed as "not a lease". This was silent and unsafe rather than merely lossy: claim's arbitration found no incumbent and granted over a live holder, and renew-lease refused to renew a lease it had just written. Without this, the GraphQL fix alone would have restored a claim that was not actually race-safe. The match is now anchored on the FIRST --> after the marker, which is also strictly more correct since an HTML comment cannot contain -->.

claim.test.sh covered only --help and usage errors, so the protocol itself passed vacuously. lease-coordination.test.sh now drives it: the happy path, the foreign-assignee conflict with its rollback, and the dropped-assignment guard. Adding those cases exposed a latent trap in that file, fixed here: a set +e / set -e 2>/dev/null || true pair that ENABLES errexit rather than restoring the file's declared set -uo pipefail mode, so every later case expecting a non-zero exit aborted the suite instead of asserting on it.

The product-code lane (overengineering)

#2897 asked for three things, all deferred until V1 shipped and the shared method stabilized. Both conditions now hold.

context/product-code-lane.md supplies the four bindings scrutiny-method.md asks a lane for. The item is the abstraction, never the file, so an interface plus its implementations plus its call sites is one finding with a members list. Eight layers ordered by how cheaply their evidence settles: single-implementation, extension-points, configuration, generality, layering, speculative-api, dead-branches, premature-async. Protected classes extend §7 with the hazards specific to changing code that runs: published API surface, serialization and wire formats, concurrency primitives, error-containment boundaries, and testability seams.

The lane's signature evidence probe is whether the second implementation ever arrived. Speculative generality is a prediction that variation would come, and tier-2 history can falsify it directly, which is what makes the lane tractable rather than a matter of a reviewer's taste.

The boundary against /simplify, code-tidying, and architecture:improve is documented as three operational handoffs. The distinguishing axis is retrospective and evidence-gated versus prospective and judgment-gated; notably, this lane never runs on a diff, because a construct introduced in the diff has no history to weigh it with.

ADR 0017 decides the lane ships as its own skill rather than an argument of overengineering:audit: skill descriptions are the routing surface and are budgeted least-invoked-first, the two lanes' trigger vocabularies are disjoint, the protected-class sets do not map, and retirement means something different in each. The shared walk orchestration is handled by extraction when the skill lands, the same move the plugin already made for the method itself.

Verification

The seam fix is verified against the live GitHub API, in the session where it previously could not run at all:

Gates run locally:

  • Seam suite: 56/56 pass. adapters/gitea/list-items.test.sh is excluded because it hangs, and it hangs identically on a clean origin/main worktree, so it is pre-existing and unrelated to this change. Flagging it rather than silently skipping.
  • shellcheck and shfmt clean on every changed shell file.
  • markdownlint-cli2, typos, editorconfig-checker clean on every changed markdown file.
  • Both plugin manifests validate against the plugin-manifest schema.
  • The repo's own ai-slop detector reports 0 findings across the new prose.

Related

Known limitation, deliberately not fixed here. Only the lease protocol is ported. get-item, list-items, list-sub-items, list-frontier, add-sub-item, and link-blocks still resolve GraphQL-only fields (issueType, blockedBy, parent, subIssues) or use gh issue list, so they remain unavailable under the same restriction. Because list-frontier is what /work-items:work uses to select, an unattended lane still cannot pick an item in a web session; only the named-item path works. The REST endpoints for most of this are served (issues list, native type, sub_issues, dependencies/blocked_by), but REST exposes no parent field, so parent_id needs its own resolution. That is a separate piece of work across five scripts and should be scoped on its own rather than widening this PR. Documented in the GitHub adapter README.


Generated by Claude Code

claude added 4 commits August 23, 2026 02:58
The seam's claim/reclaim verbs resolved assignees through `gh issue edit
--add-assignee` and `gh issue view --json assignees`, both of which route
through GitHub's GraphQL API. Sandboxed sessions (Claude Code on the web and
remote execution) serve only a pinned set of GraphQL operations and refuse the
rest with HTTP 403, so the entire lease protocol was unrunnable there and no
work-items lane could acquire a race-safe claim.

Port both verbs to the REST `.../issues/<n>/assignees` endpoints via shared
helpers in common.sh. The helpers take the same read/write writer argument the
adapter already used, so the carve-out that assigns the session identity rather
than the bot is preserved; `@me` is resolved to the login explicitly because
REST takes a literal login.

Two defects surfaced while verifying the port end to end:

- REST POST /assignees returns 201 and silently drops a login that cannot be
  assigned, where the GraphQL path failed loudly. Unguarded, that turns a
  no-op assignment into a reported claim while list-frontier still sees the
  item unassigned, putting two workers on one item. claim now re-reads the
  assignees and exits 4 before posting any lease comment.

- wit_lease_json matched only a body ENDING in ` -->`, so a lease comment with
  anything appended (a bot wrapper's attribution footer, a signature, a CI
  note) parsed as "not a lease". This was silent and unsafe: claim's
  arbitration found no incumbent and granted over a live holder, and
  renew-lease refused to renew a lease it had just written. Anchor on the first
  ` -->` after the marker, which is also strictly more correct since an HTML
  comment cannot contain `-->`.

claim.test.sh covered only --help and usage errors, so the protocol itself
passed vacuously. lease-coordination.test.sh now drives the happy path, the
foreign-assignee conflict with its rollback, and the dropped-assignment guard.

Verified against the live API: claim, renew-lease, and back-off-on-live-lease
all succeed in a session where they previously could not run at all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RiAekqvuMHJs3CjusBbNWH
…hape

Closes #2897.

The deferred second lane from the overengineering-detection-skill interview.
V1 (#2961) shipped the enforcement surface and wrote the scrutiny method
lane-reusable at the plugin level, whose "Lane binding" section forward-
referenced "a future product-code lane" with nowhere to go. Both conditions
that blocked this issue now hold: V1 shipped, and the method stabilized.

The issue asked for three things.

Walker and evidence sources: context/product-code-lane.md supplies the four
bindings the method asks a lane for. The item is the abstraction, never the
file, so an interface plus its implementations plus its call sites is one
finding with a members list. Eight layers, ordered by how cheaply their
evidence settles: single-implementation, extension-points, configuration,
generality, layering, speculative-api, dead-branches, premature-async. The
evidence table maps each tier onto sources this lane actually has, and names
the lane's signature tier-2 probe: whether the second implementation ever
arrived. That is what makes speculative generality checkable, since the
generality was a prediction that variation would come, and history can falsify
it rather than leaving the call to a reviewer's taste. Protected classes extend
the method's set with the hazards specific to changing code that runs:
published API surface, serialization and wire formats, concurrency primitives,
error-containment boundaries, and testability seams.

Boundary against existing owners: documented as three operational handoffs
rather than a declaration. The distinguishing axis is retrospective and
evidence-gated versus prospective and judgment-gated. A "keep but reshape"
finding belongs to architecture:improve, a safe mechanical one to
code-tidying, and the lane never runs on a diff because a construct introduced
in the diff has no history to weigh it with.

Shipping shape: ADR 0017 records it as its own skill rather than an argument of
overengineering:audit. Skill descriptions are the routing surface and are
budgeted least-invoked-first, the two lanes' trigger vocabularies are disjoint,
the protected-class sets do not map, and retirement means something different
in each. The shared walk orchestration is handled by extraction when the skill
lands, the same move the plugin already made for the method itself, not by
fusing the two lanes.

No skill or behavior changes: the specification precedes its skill, and points
at the enforcement lane's copy of the shared rules rather than duplicating them
so there is one statement of each rule through the transition.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RiAekqvuMHJs3CjusBbNWH
Resolves a CHANGELOG conflict: main shipped work-items 0.39.14 (the generated
options-block docs fix) while this branch had also claimed 0.39.14. Main's
entry is preserved as 0.39.14 and this branch's entries move to 0.39.15, with
the manifest bumped to match.

Also fixes a latent trap found while adding the claim cases:
lease-coordination.test.sh wrapped one call in `set +e` and "restored" with
`set -e 2>/dev/null || true`, which ENABLES errexit rather than restoring the
file's declared `set -uo pipefail` mode, so every later case expecting a
non-zero exit aborted the suite instead of asserting on it. Both sites now use
`|| rc=$?`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RiAekqvuMHJs3CjusBbNWH
Resolves an overengineering version collision: main shipped the delta lane as
0.2.0 (#3146) while this branch had also claimed 0.2.0. Main's entry stays at
0.2.0 and this branch's documentation-only entries move to 0.2.1, taking main's
plugin description (which now names the third lane).

The delta lane also shipped as its OWN skill composing audit, rather than as an
argument selecting a mode inside it, which is independent corroboration of the
decision ADR 0017 records. The ADR now cites it, and its context section is
corrected: #2897 said "a third skill" when the plugin had two, but delta has
taken that slot, so the product-code lane is the fourth skill and third lane.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RiAekqvuMHJs3CjusBbNWH
@claude

claude Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

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


Security review in progress

  • Gather context (PR diff, changed files)
  • Apply skip gate check — has security-relevant surface (GitHub API auth/assignee operations), continuing full review
  • Review shell scripts for command injection / unsafe gh usage
  • Review GitHub Actions / workflow-adjacent changes — none present in this PR
  • Review lease/assignment logic for auth/trust-boundary issues
  • Adversarially validate candidate findings (independent pass running)
  • Post findings / summary

@github-actions

github-actions Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Last security-reviewed head: 1f17f7f95e7f247cea9dabd7bd3c31a569b01dba. 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 23, 2026

Copy link
Copy Markdown
Contributor

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


Code review complete

  • Gather context (PR body, changed files)
  • Read diff against origin/main (via the two feature commits e1a00db / 9772549, since a shallow clone plus network restrictions in this session blocked a direct origin/main...HEAD diff — see note below)
  • Review work-items REST-lease changes (claim.sh, reclaim.sh, common.sh, lib/lease.sh)
  • Review test changes (lease-coordination.test.sh, lease.test.sh)
  • Review overengineering doc/ADR changes
  • Post findings as inline comments (none warranted — see below)
  • Post summary

Summary

I read this PR end to end against the two feature commits and did not find correctness bugs to flag. No inline comments were posted because nothing localizes to a single changed line as a defect — the following is prose review only.

work-items REST lease port

  • The wit_read_assignees / wit_add_assignee / wit_remove_assignee / wit_try_remove_assignee helpers (common.sh#L158) correctly preserve the read/write writer-argument identity routing that the bare-gh-for-@me carve-out in the README depends on, and wit_try_remove_assignee intentionally bypasses wit_run_gh (always bare gh, swallows failure) which matches the || true best-effort rollback semantics it replaces.
  • The dropped-assignment guard in claim.sh (claim.sh#L69) runs after the EXIT trap is armed, so a silently-dropped POST still triggers the (harmless, no-op) rollback — correctly sequenced, and it exits EX_AUTH (4) before any lease comment is posted, matching the stated invariant.
  • wit_lease_json's switch to first---> anchoring (lease.sh#L20) is sound: since the marker opens an HTML comment and HTML comments cannot contain -->, the first occurrence always closes it, so trailing content after the marker no longer causes a live lease to parse as absent.
  • reclaim.sh is a faithful call-site swap with no behavior change (still routes failures loudly through wit_run_gh, unlike the best-effort claim rollback).
  • The set +e / set -e 2>/dev/null || true|| rc=$? fix in lease-coordination.test.sh is a real and correctly-diagnosed bug (that pattern enables errexit rather than restoring the file's set -uo pipefail baseline); the new claim.sh scenarios (happy path, foreign-assignee conflict + rollback, dropped-assignment guard) exercise real gaps claim.test.sh left uncovered.
  • I did not find a new race introduced by the REST port: the co-assignment corner case (two workers both landing as co-assignees and both backing off on seeing "another login") is a property of the pre-existing sole-assignee-check design, unchanged by this port.

overengineering product-code lane + ADR 0017

  • Cross-references resolve: product-code-lane.md's links to ../skills/audit/context/surface-walk.md and ../../../docs/adr/0017-...md, and the ADR's references back into product-code-lane.md, all point at real paths.
  • scrutiny-method.md's "Lane binding" section update is consistent with the new document — the forward reference to "a future product-code lane" is replaced with a link to the concrete spec.
  • Version/changelog bumps (work-items 0.39.13→0.39.15, overengineering 0.2.0→0.2.1 after merge with the delta-lane bump on main) are internally consistent between plugin.json and CHANGELOG.md.

Note on verification limits in this session: this sandbox's git fetch/git fetch --unshallow and running the shell test suites (bash lease.test.sh, bash lease-coordination.test.sh) both required interactive approval that wasn't available here, so I reviewed via the locally-available feature commits (e1a00db, 9772549) rather than a live origin/main...HEAD diff, and via static reading rather than executing the test suites myself. The PR body's own verification section (56/56 seam suite, shellcheck/shfmt/markdownlint clean, live-API verification against #2897) covers that gap.

Verdict: no blocking issues found. This is a clean, well-tested seam fix plus a doc-only spec addition with consistent cross-references.

Branch: claude/work-items-integration-h3yabz

@github-actions

github-actions Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

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

Second overengineering version collision in a row: main took 0.2.1 for the
findings-artifact date-rationale fix (#3167) after taking 0.2.0 for the delta
lane (#3146). Main's entries stay where they are and this branch's
documentation-only entry moves to 0.2.2.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RiAekqvuMHJs3CjusBbNWH
@claude

claude Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

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


Security review complete

  • Gather context (PR diff since last reviewed head)
  • Apply skip gate check
  • Post final summary

Skip gate: no re-review needed. The last full security review covered head e59ff938. The current head (1f17f7f) only merges in unrelated origin/main commits (24ace40, a source-control fix) — git diff e59ff938 1f17f7f touches none of this PR's substantive files. The only overlapping file is plugins/overengineering/CHANGELOG.md/plugin.json, and that diff is purely a version-bump conflict resolution (0.2.10.2.2) from main's independent changelog entry, with no new prose or logic.

None of the security-relevant surface — claim.sh, common.sh, lease.sh, reclaim.sh, lease-coordination.test.sh, lease.test.sh, the ADR, or product-code-lane.md — changed since that review. Its verdict stands: no blocking security issues found in the REST-lease assignee-identity routing, the dropped-assignment guard, or the wit_lease_json anchoring fix.

Verdict: no new security-relevant changes since the last review at e59ff938; that review's "no blocking issues" verdict applies unchanged to head 1f17f7f.

Branch: claude/work-items-integration-h3yabz

@claude

claude Bot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

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


/review:code-review — CI code-review lane

  • Gather context (PR body, changed files, custom REPO/PR/HEAD SHA header)
  • Diff each changed file's blob (origin/main:<path> vs HEAD:<path>) — shallow clone has no merge-base for origin/main...HEAD, so per-file blob diffs were used instead; confirmed the 14-file set matches the PR's stated changed files
  • Review work-items REST-lease port (common.sh, claim.sh, reclaim.sh, lease.sh) for correctness
  • Review test changes (lease-coordination.test.sh, lease.test.sh) for coverage and stub correctness
  • Review overengineering doc/ADR changes for internal consistency (section cross-references, version/changelog bumps)
  • Post findings

Findings

None. This lane found no correctness or maintainability issues a careful reviewer would block on.

Specifically checked and confirmed sound:

  • wit_lease_json's new first---> anchoring (lease.sh#L21-30) uses ${body%% -->*} — bash's %% removes the longest matching suffix, which is what anchors on the first --> occurrence rather than the last. This is the correct glob direction for the stated intent (not just plausible-sounding).
  • claim.sh's dropped-assignment guard (claim.sh#L61-74) re-reads assignees via a fresh wit_read_assignees call (not the POST response body) before trusting the assignment landed, and runs after the rollback trap is armed, so a silent drop still gets cleaned up.
  • The read/write writer-argument routing through the new wit_read_assignees/wit_add_assignee/wit_remove_assignee helpers (common.sh#L158-196) preserves the session-identity carve-out for claim/reclaim.sh call sites — parameter order matches at every call site.
  • lease-coordination.test.sh's set +e / set -e 2>/dev/null || true|| rc=$? fix is a real bug fix (the old pattern enabled errexit rather than restoring the file's set -uo pipefail baseline), and the new claim.sh scenarios exercise real gaps claim.test.sh left uncovered.
  • overengineering doc changes: all §N cross-references in product-code-lane.md and the ADR resolve to actual section numbers in scrutiny-method.md (1-12); plugin.json/CHANGELOG.md version bumps are consistent for both plugins (work-items 0.39.14→0.39.15, overengineering 0.2.1→0.2.2).

No inline comments posted — nothing localizes to a specific line as a defect.

Note: this sandbox's shallow clone has no merge-base with origin/main, so I diffed each changed file's blob content directly by path (git diff origin/main:<path> HEAD:<path>) rather than a single origin/main...HEAD diff. This is exact per-file but wouldn't by itself catch a file deleted by this PR that isn't in the stated changed-files list; the provided 14-file list was used as the scope boundary.

Branch: claude/work-items-integration-h3yabz

@kyle-sexton
kyle-sexton merged commit a60acac into main Aug 23, 2026
50 checks passed
@kyle-sexton
kyle-sexton deleted the claude/work-items-integration-h3yabz branch August 23, 2026 06:36
kyle-sexton pushed a commit that referenced this pull request Aug 23, 2026
Second CHANGELOG collision: main shipped its own 0.39.15 (#3166 — lease
marker parsing, and the GraphQL removal from claim/reclaim), so this work
moves to 0.39.16 and both entries are kept, newest-first.

The two changes are complementary halves of one problem rather than
overlapping work, and the changelog entry now says so: a sandboxed
session has both an old gh from Ubuntu's archives and a GraphQL surface
that answers only a pinned operation set. #3166 took GraphQL out of the
lease path; this branch stops the version floor refusing that path at the
dispatcher. Verified composing on gh 2.45 with GraphQL 403: `claim`
against a nonexistent issue now returns a REST 404, where before #3166 it
was 403 and before this branch it was exit 3.

No source conflict between them — #3166 touched claim.sh, reclaim.sh,
common.sh and lib/lease.sh; this branch touches the dispatcher. All 18
seam suites pass on the merged tree (242 assertions).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012RvxCrBC5rq1Sv8oMBDriB
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.

overengineering: product-code lane — apply the scrutiny method to code-level overengineering in application code

2 participants