github-coordination sync listed every repo issue and pushed the epic label onto all of them (#3084). Scope the listing to issues carrying the policy's epic label plus issues whose body still holds the coordination marker (label-drift recovery), deduped by number, and reject an empty labels.epic in loadPolicy. Tests cover the filtered path and the recovery path with exact gh argv. Independent exact-head review passed with no P0/P1; CI 44/44 at the head.
actions.js:
- Add assertValidRepo/assertValidIssueNumber guards at the top of all
action handlers (applyClaim, applySync, applyValidate, applyPublish,
applyReview, applyDecompose, applyUnblock) for fast-fail validation
- applyValidate: fix status transition — set 'validated' unconditionally
when ok=true instead of preserving 'blocked' (was inconsistent with
projectState becoming 'ready')
gh-api.js:
- runGh: preserve GITHUB_TOKEN by default; only delete when caller
explicitly sets options.stripGithubToken=true (was deleting by
default, breaking CI)
parsing.js:
- extractCoordinationState: throw SyntaxError on malformed JSON instead
of silently returning null — lets callers distinguish bad JSON from
absent marker
- normalizeBodyForComparison: fix regex to match JSON-quoted form
"lastSyncAt": ... instead of bare lastSyncAt: ...
policy.js:
- loadPolicy: validate that parsed JSON is a plain object before
spreading; coerce nested fields (labels, review, validation,
branchModel, project, fieldNames) to objects before merging
state.js:
- assertIssueClaimable: block re-claim on status alone (not status AND
owner) to prevent {status:'claimed', owner:null} bypass; use
state.owner || 'unknown' in error message
- getCoordinationState: catch SyntaxError from extractCoordinationState,
log warning to stderr, fall back to default state
tests/lib:
- Update malformed-JSON test to expect SyntaxError throw instead of null
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>