affaan-m/ECC · error · Error

Unknown control-pane action: ${actionId}

Error message

Unknown control-pane action: ${actionId}

What it means

Thrown by buildControlPaneAction at scripts/lib/control-pane/actions.js:97-100 when actionId is not a key in the ACTION_DEFINITIONS Map. The control pane exposes a fixed set of ECC2 graph actions — sync-knowledge, recall-knowledge, graph-sync, open-dashboard — each mapped to a cargo invocation against the ecc2 workspace. Unknown ids are rejected before any cargo command is constructed. The function is called both directly from the HTTP server's POST /api/actions/:id route (server.js:271) and from buildControlPaneActions which iterates all known keys.

Source

Thrown at scripts/lib/control-pane/actions.js:99

  const text = String(value);
  if (text.length === 0) return "''";
  if (/^[A-Za-z0-9_./:=@%+-]+$/.test(text)) return text;
  return `'${text.replace(/'/g, `'\\''`)}'`;
}

function commandLineFor(action) {
  return [
    `cd ${shellQuote(action.cwd)}`,
    '&&',
    shellQuote(action.command),
    ...action.args.map(shellQuote),
  ].join(' ');
}

function buildControlPaneAction(actionId, options = {}) {
  const definition = ACTION_DEFINITIONS.get(actionId);
  if (!definition) {
    throw new Error(`Unknown control-pane action: ${actionId}`);
  }

  const repoRoot = path.resolve(options.repoRoot || process.cwd());
  const cwd = path.join(repoRoot, 'ecc2');
  const limit = normalizeLimit(options.limit);
  const query = String(options.query || '').trim();
  const args = definition.args({ limit, query });
  const action = {
    id: actionId,
    label: definition.label,
    description: definition.description,
    command: 'cargo',
    args,
    cwd,
    executable: definition.executable,
  };

  return {

View on GitHub (pinned to 01e15490f0)

Solutions

  1. Use one of the known ids: sync-knowledge, recall-knowledge, graph-sync, open-dashboard.
  2. Call GET /api/snapshot or buildControlPaneActions() to enumerate the live action ids for your version.
  3. If you need a new action, register it in the ACTION_DEFINITIONS Map at actions.js:5-72 (label, description, args factory, executable flag).
  4. Note: even valid ids that are non-executable (open-dashboard, executable:false) return a 400 from the server rather than running — see server.js:277-285.

Example fix

// before
fetch('http://127.0.0.1:8765/api/actions/sync', { method: 'POST' });
// after
fetch('http://127.0.0.1:8765/api/actions/sync-knowledge', { method: 'POST', body: '{}' });
Defensive patterns

Strategy: validation

Validate before calling

const KNOWN = new Set(['sync-knowledge', 'recall-knowledge', 'graph-sync', 'open-dashboard']);
function assertKnownAction(actionId) {
  if (!KNOWN.has(actionId)) throw new Error(`Unknown control-pane action: ${actionId}`);
  return actionId;
}

Type guard

function isControlPaneActionId(value) {
  return typeof value === 'string'
    && ['sync-knowledge', 'recall-knowledge', 'graph-sync', 'open-dashboard'].includes(value);
}

Prevention

When it happens

Trigger: POST /api/actions/sync to the control-pane server (truncated/misspelled id); POST /api/actions/recall-knowledge%2Fextra (path-traversal-ish id); a UI button wired to a stale action id after an upgrade that renamed or removed an action; calling buildControlPaneAction('refresh', {}) from a script.

Common situations: Custom UI dashboards referencing actions that were renamed across versions; URL-encoded typos; clients POSTing the action label ('Sync Knowledge') instead of the id ('sync-knowledge'); attackers probing arbitrary action ids against the loopback server.

Related errors


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