Skip to content

work-items:work-loop: 4 audit findings (compound-shell classifier risk, unvalidated persisted state, exit-condition prose, no pattern-escalation) #1668

Description

@kyle-sexton

Filed via /plugin-quality:audit (interactive session, human-confirmed before filing).

Four findings from auditing work-items:work-loop v0.25.3 after live use across 5 drain cycles in one session (2026-07-26). claude plugin validate passed; skill-quality:check passed (0 errors). All doc citations fetched 2026-07-26 against Claude Code 2.1.220.

F1 (IMPORTANT) — mandated telemetry-upsert script is compound-shaped, exactly what tripped the auto-mode classifier live this session

Location: SKILL.md "Telemetry and durable loop state" — the inlined #502 upsert: variable assignment, a LOOKUP() function, an if/else, a for loop, four gh api calls, mandated as one Bash invocation because "an installed plugin cannot invoke a sibling plugin's scripts."

A separate compound gh api command was blocked mid-session by the auto-mode classifier, resolved only by splitting into isolated calls.

Doc grounding: https://code.claude.com/docs/en/permission-modes documents no named rule for "compound/chained commands" specifically — the belief that compound shapes are blocked is an empirically-observed working theory, not a documented guarantee. https://code.claude.com/docs/en/auto-mode-config is the actual lever: narrow permissions.allow Bash rules bypass the classifier entirely unless autoMode.classifyAllShell: true is set.

Remediation, cheapest → most ambitious:

  1. Add a Gotcha noting the retry-as-isolated-calls workaround.
  2. Ship the upsert as this plugin's own bundled script (e.g. ${CLAUDE_PLUGIN_ROOT}/tools/work-item-tracker/lane-telemetry-upsert.sh), invoked as one non-compound command — legitimate since it's the plugin's OWN script, not a sibling's; also closes an SSOT gap (versioned/testable vs. hand-copied prose-as-shell). Confidence that this reduces classifier scrutiny is inferred, not independently tested.
  3. Most ambitious: an operator-configured autoMode.allow entry (a user/org decision, never a plugin default).

F2 (SUGGESTION) — Exit-condition prose can be misread as an already-eligible candidate count

Location: SKILL.md "Exit condition" item 1: "list-frontier --autonomous returns no candidates." That verb only excludes needs-human-labeled items — it does not filter to agent-ready (the underlying seam-semantics gap is already tracked at #1561). The Admission gate section correctly re-derives eligibility per-item, but the identical Exit-condition phrase nearly caused a real miscount in the audited session (28 raw items vs. an assumed 2), caught only incidentally before any dispatch happened.

Remediation: one-clause caveat next to the Exit-condition's list-frontier --autonomous reference, pointing at the Admission gate section and #1561.

F3 (IMPORTANT) — durable telemetry state has no bounds validation on read

Location: SKILL.md "Telemetry and durable loop state" / "Adaptive item cap." A persisted item_cap: 5 was found with no userConfig override present anywhere in the repo, against a manifest ceiling default of 3 (verified directly: work_loop_item_cap_start=2, _ceiling=3, _floor=1). Nothing validates a persisted numeric field against the configured bounds before trusting it, on state the skill's own text calls "the source of truth" and already documents as race-prone across sessions/lanes (the "Creation race reconcile" section).

Remediation, cheapest → most ambitious:

  1. Clamp item_cap to resolved bounds immediately after the durable-state read in "Cycle shape" step 1, logging a correction when it fires.
  2. Extend to full-block validation (range/enum-check every field), quarantining a structurally invalid document rather than trusting it wholesale.

F4 (SUGGESTION, open design question) — no pattern-level escalation for a recurring upstream defect

Location: SKILL.md "Admission gate" table, Unclassified row. The gate correctly fail-closed 7+ times across the audited session against the same already-tracked upstream defect (see #1288), each occurrence costing a real GitHub write, with no rollup/louder-escalation mechanism when the SAME root cause recurs repeatedly within one drain.

Remediation: track a same-gate-reason streak in durable state; past a threshold, post one rollup escalation on the upstream item, or surface an aggregate telemetry line, instead of N silent per-item escalations.

Positive control (no action needed)

The cross-session label-mutation hazard the skill's own "Manual-check step" is explicitly built to catch (citing #815/#816/#965) fired correctly under real pressure this session (a stray agent-ready re-application on #902 with no ratification comment behind it was detected and reverted).


Related: #1561 (list-frontier --autonomous seam semantics, cited not duplicated), #1288 (recurring unclassified-agent-ready upstream defect, cited not duplicated).

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: securitySecurity-relevant: vulnerability, hardening, or disclosure follow-up.needs-humanHuman-in-the-loop required; autonomous sessions must not resolve items carrying this.priority: highSignificant impact, or blocks an imminent release; staff this cycle.status: needs-decisionAwaiting a human or maintainer judgment call.work-class: structuralRefactors, migrations, contract changes; cross-cutting and hard to reverse.

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions