affaan-m/ECC · error

${result.error || 'request failed'}

Error message

${result.error || 'request failed'}

What it means

Thrown by postWorkItem in the control pane UI after the server replies with a JSON body whose ok field is false (HTTP status was 200; readJsonResponse already handled non-2xx). The message is whatever the server put in result.error — typically one of the work-item mutation rejections: work item not found, invalid lane, claim requires an owner, or item already done.

Source

Thrown at scripts/lib/control-pane/ui.js:664

    }

    $('#query-form').addEventListener('submit', event => {
      event.preventDefault();
      state.query = $('#query').value.trim();
      load().catch(error => showError('#app', error));
    });
    $('#refresh').addEventListener('click', () => {
      load().catch(error => showError('#app', error));
    });

    async function postWorkItem(pathSuffix, payload) {
      const response = await fetch('/api/work-items/' + pathSuffix, {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify(payload || {})
      });
      const result = await readJsonResponse(response);
      if (!result.ok) throw new Error(result.error || 'request failed');
      await load();
    }

    window.eccClaimItem = function (id) {
      if (!state.allowActions) return;
      const owner = window.prompt('Claim "' + id + '" as (owner name):');
      if (!owner) return;
      const as = (window.prompt("Owner kind: 'agent' or 'human'", 'human') || '').trim().toLowerCase();
      postWorkItem(encodeURIComponent(id) + '/claim', { owner: owner.trim(), as: as === 'agent' ? 'agent' : 'human' })
        .catch(error => showError('#app', error));
    };
    window.eccMoveItem = function (id, lane) {
      if (!state.allowActions) return;
      postWorkItem(encodeURIComponent(id) + '/move', { lane })
        .catch(error => showError('#app', error));
    };

    // Live board: refresh on a gentle interval; pause while a prompt/tab is hidden.

View on GitHub (pinned to d8409a4b08)

Solutions

  1. Read the surfaced message — it names the exact server-side rejection (e.g. 'Work item not found: X' or 'Invalid lane'), then fix that condition
  2. Click Refresh to reload current board state before retrying the action
  3. When claiming, make sure the owner prompt is answered with a non-empty name
  4. If the item was finished by another worker, pick a different item — the refusal is correct behavior
Defensive patterns

Strategy: try-catch

Validate before calling

if (!id) return;
const fresh = state.items.find(i => i.id === id);
if (!fresh) { await load(); return; } // stale card; reload instead of posting

Try / catch

try {
  await postWorkItem(`${encodeURIComponent(id)}/claim`, { owner });
} catch (error) {
  showError('#app', error);
  if (/not found|already done/.test(error.message)) {
    await load(); // stale state — refresh the board, don't alarm the user
  }
}

Prevention

When it happens

Trigger: Clicking Claim or Move on a card in a stale board where the item was deleted or already completed by someone else; submitting a claim with an empty owner; posting to /api/work-items/<id>/claim or /move with a payload the mutation layer rejects.

Common situations: Two agents, or a human plus an agent, using the control pane concurrently against the same DB; a UI tab left open while the underlying work-items DB changed; canceling or blanking the browser prompt dialogs.

Understand the failure class

Background: "API request failed": what wrapped HTTP errors from external APIs mean and how to find the real cause — this error's family across 29 libraries.

Related errors


AI-assisted analysis of affaan-m/ECC@d8409a4b08 (2026-08-26). Data as JSON: /api/errors/4ef552d141bcb28a. Report an issue: GitHub.