affaan-m/ECC · error · Error

invalid issueNumber: expected positive integer, got ${JSON.s

Error message

invalid issueNumber: expected positive integer, got ${JSON.stringify(issueNumber)}

What it means

Thrown by assertValidIssueNumber when issueNumber is not a finite positive integer. GitHub issue numbers are positive integers; negatives, zero, fractions, NaN, or non-numeric strings all make gh issue view fail or target the wrong issue. The guard uses Number.isFinite + > 0 + Number.isInteger to reject all malformed inputs before any gh call.

Source

Thrown at scripts/lib/github-coordination/actions.js:27

  buildIssueStateFromAction,
  desiredLabelsForState,
  getCoordinationState,
  summarizeStateForOutput,
  syncIssueLabels,
  verifyDependenciesClosed,
} = require('./state');
const { upsertCoordinationWorkItem } = require('./store');
const { extractIssueReferences, extractTasks } = require('./parsing');

function assertValidRepo(repo) {
  if (typeof repo !== 'string' || !repo.trim()) {
    throw new Error(`invalid repo: expected non-empty string, got ${JSON.stringify(repo)}`);
  }
}

function assertValidIssueNumber(issueNumber) {
  if (!Number.isFinite(issueNumber) || issueNumber <= 0 || !Number.isInteger(issueNumber)) {
    throw new Error(`invalid issueNumber: expected positive integer, got ${JSON.stringify(issueNumber)}`);
  }
}

function staleCoordinationLabels(issue, nextLabels, policy) {
  const epicLabel = policy.labels && policy.labels.epic;
  return normalizeLabels(issue.labels).filter(l =>
    (l.startsWith('coordination:') || l === epicLabel) && !nextLabels.includes(l)
  );
}

// applyClaim performs a read (getIssue) → check (assertIssueClaimable) → write
// (editIssue) sequence that is NOT atomic. Two concurrent callers can both read
// an unclaimed issue, pass the check, and both succeed — resulting in a
// double-claim. A code-review finding suggested fixing this via
// context.store.acquireLock(repo, issueNumber), but that API does not exist in
// store.js; adding a call to it would throw at runtime. Left as-is until a
// locking primitive is available — callers should prevent races via external
// serialization (e.g. a serialized job queue or GitHub branch-protection rule).

View on GitHub (pinned to 01e15490f0)

Solutions

  1. Pass a positive integer (e.g. 42) — convert and validate before calling.
  2. Strip leading '#' and parse: const n = Number.parseInt(String(raw).replace(/^#/, ''), 10); assert Number.isInteger(n) && n > 0.
  3. Validate at the CLI/config boundary so the coordination functions receive clean numbers.
  4. Add a unit test with 0, -1, 3.5, NaN, 'abc' to confirm the guard.

Example fix

// before
applyPublish(repo, rawIssue);

// after — coerce and validate
const issueNumber = Number.parseInt(String(rawIssue).replace(/^#/, ''), 10);
if (!Number.isInteger(issueNumber) || issueNumber <= 0) {
  throw new Error(`issue number must be a positive integer, got ${rawIssue}`);
}
applyPublish(repo, issueNumber);
Defensive patterns

Strategy: validation

Validate before calling

if (!Number.isInteger(issueNumber) || issueNumber <= 0) {
  throw new Error(`issueNumber must be a positive integer, got ${JSON.stringify(issueNumber)}`);
}

Type guard

function isPositiveInt(n) {
  return Number.isInteger(n) && n > 0;
}

Try / catch

try {
  applyPublish(repo, issueNumber);
} catch (e) {
  if (/invalid issueNumber/.test(e.message)) { console.error('Pass a positive integer issue number'); process.exit(2); }
  throw e;
}

Prevention

When it happens

Trigger: Passing issueNumber: 0, -5, 3.5, NaN, 'abc', or undefined; reading the number from a string ('#42') without stripping the '#'; parseInt on an empty string yielding NaN.

Common situations: CLI arg parsed as string not converted to number; issue ref like '#42' passed raw; URL param not validated; off-by-one or default 0 sentinel leaked into the call.

Related errors


AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13). Data as JSON: /api/errors/0045097ade6d8beb. Report an issue: GitHub.