affaan-m/ECC · error

Issue # is not open

Error message

Issue #${issue.number} is not open

What it means

assertIssueClaimable validates that an issue can be claimed: the underlying GitHub issue must be in the 'open' state. It throws when the issue is closed (or has an unexpected state), preventing agents from claiming work on issues that are finished or rejected. A further check rejects issues already claimed by another owner.

Solutions

  1. Check the issue on GitHub — if closed intentionally, pick a different issue.
  2. If the issue should be open, reopen it (`gh issue reopen <n> -R <owner>/<name>`) and retry the claim.
  3. Re-sync/re-fetch the issue to get the latest state before claiming.
  4. If using a shim, ensure it returns state as the lowercase string 'open'.

Example fix

// before
applyClaim('org/repo', 42); // issue 42 is closed
// after
runGh(['issue', 'reopen', '42', '-R', 'org/repo']);
applyClaim('org/repo', 42);
Defensive patterns

Strategy: try-catch

Validate before calling

const issue = getIssue(repo, n);
if (String(issue.state || '').toLowerCase() !== 'open') {
  throw new Error(`Issue #${n} is ${issue.state}; only open issues can be claimed`);
}

Type guard

const isOpenIssue = (i) => typeof i?.state === 'string' && i.state.toLowerCase() === 'open';

Try / catch

try {
  applyClaim(repo, n);
} catch (e) {
  if (e.message.endsWith('is not open')) {
    console.error(`Issue #${n} is closed. Reopen it (gh issue reopen ${n}) or pick another issue.`);
  } else { throw e; }
}

Prevention

When it happens

Trigger: Calling applyClaim (directly or via the claim workflow) for an issue whose state is 'closed' — fetched from getIssue/listIssues — or whose state field is missing/case-mismatched in a shim.

Common situations: A collaborator closed the issue between listing and claiming, automation auto-closed stale issues, a bot closed issues on branch merge, or a custom shim returns state as 'OPEN' in an unexpected format or omits it.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/8dd46458424ce4d6. Report an issue: GitHub.

Appendix: source

Thrown at scripts/lib/github-coordination/state.js:211

    action,
    status: state.status,
    owner: state.owner || null,
    branch: state.branch || null,
    validation: state.validation || 'pending',
    review: state.review || 'not-requested',
    project: summarizeProjectProjection(state, policy),
    dependencies: Array.isArray(state.dependencies) ? state.dependencies : [],
    tasks: Array.isArray(state.tasks) ? state.tasks : [],
    labels: normalizeLabels(issue.labels),
    workItemId: `github-${slugifySegment(repo)}-epic-${issue.number}`,
    lastActionAt: state.lastActionAt || null,
    lastSyncAt: state.lastSyncAt || null,
  };
}

function assertIssueClaimable(issue, state) {
  if (String(issue.state || '').toLowerCase() !== 'open') {
    throw new Error(`Issue #${issue.number} is not open`);
  }

  if (state.status === 'claimed') {
    throw new Error(`Issue #${issue.number} is already claimed by ${state.owner || 'unknown'}`);
  }
}

function verifyDependenciesClosed(repo, dependencyNumbers, options = {}, allIssues = null) {
  if (!Array.isArray(dependencyNumbers) || dependencyNumbers.length === 0) {
    return [];
  }

  const issueList = allIssues || listIssues(repo, { ...options, state: 'all', limit: options.limit || 200 });
  const closed = [];
  for (const dependencyNumber of dependencyNumbers) {
    const issue = findIssueByNumber(issueList, dependencyNumber);
    if (!issue) {
      process.stderr.write(`[github-coordination] Warning: dependency issue #${dependencyNumber} not found in issue list (may be in a different repo or beyond limit)\n`);

View on GitHub (pinned to 8321021c54)