jackwener/OpenCLI · error · ArgumentError

status "${status}" not in {${TASK_STATUSES.join('|')}} — pre

Error message

status "${status}" not in {${TASK_STATUSES.join('|')}} — pre-network reject (saves a 400 round-trip).

What it means

task-status validates the target status against TASK_STATUSES before touching the network, throwing an ArgumentError with the allowed set and an explicit note that this is a pre-network reject to save a 400 round-trip. The PATCH request is only issued once the status token is known-valid.

Source

Thrown at clis/slock/task-status.js:41

  access: 'write',
  description: `Set a task's status (PATCH /tasks/:taskId/status, body {status}). One of ${TASK_STATUSES.join('|')}.`,
  domain: SLOCK_DOMAIN,
  strategy: Strategy.COOKIE,
  browser: true,
  siteSession: 'persistent',
  args: [
    { name: 'taskId', positional: true, required: true, help: 'Full task UUID (= message id; short ids rejected)' },
    { name: 'status', positional: true, required: true, help: `One of: ${TASK_STATUSES.join('|')}` },
    { name: 'server', help: 'Override active server' },
  ],
  columns: ['taskId', 'taskStatus', 'assigneeId', 'taskNumber'],
  func: async (page, kwargs) => {
    let id;
    try { id = assertMessageIdShape(String(kwargs.taskId ?? '')); }
    catch (e) { throw new ArgumentError(e.message); }
    const status = String(kwargs.status ?? '').trim();
    if (!TASK_STATUSES.includes(status)) {
      throw new ArgumentError(`status "${status}" not in {${TASK_STATUSES.join('|')}} — pre-network reject (saves a 400 round-trip).`);
    }
    await page.goto(SLOCK_HOME_URL);
    const snippet = `
      ${authHeadersFragment({ serverScoped: true, serverIdOverride: kwargs.server })}
      const res = await fetch('${SLOCK_API_BASE}/tasks/' + encodeURIComponent(${JSON.stringify(id)}) + '/status', {
        method:'PATCH', credentials:'include', headers,
        body: JSON.stringify({ status: ${JSON.stringify(status)} }),
      });
      if (res.status === 400) {
        const j = await res.json().catch(() => ({}));
        return { kind: 'http', status: 400, where: '/tasks/:taskId/status (bad request: ' + (j.error || j.message || 'invalid status transition') + ')' };
      }
      if (res.status === 403) return { kind: 'http', status: 403, where: '/tasks/:taskId/status (forbidden — terminal status (done/closed), not the assignee, or channel archived)' };
      if (res.status === 404) return { kind: 'http', status: 404, where: '/tasks/:taskId/status (task not found)' };
      // F6 — actionable hint for repeat-set 409 ("status already X").
      if (res.status === 409) return { kind: 'http', status: 409, where: '/tasks/:taskId/status (conflict — task is already in status ' + ${JSON.stringify(status)} + '; no-op set rejected)' };
      if (!res.ok) return { kind: res.status===401?'auth':'http', status: res.status, where:'/tasks/:taskId/status' };
      const data = await res.json().catch(() => ({}));

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Run `slock task-status --help` and pass exactly one of the listed statuses
  2. Normalize casing/spelling to the canonical tokens (e.g. 'done' → whatever TASK_STATUSES defines)
  3. Validate the value in your wrapper before invoking to give users a friendlier message
  4. Present a picker/autocomplete of TASK_STATUSES instead of free-text input

Example fix

// before
await cli('slock', 'task-status', '--taskId', id, '--status', 'Done');
// after
await cli('slock', 'task-status', '--taskId', id, '--status', 'done'); // exact member
Defensive patterns

Strategy: validation

Validate before calling

import { TASK_STATUSES } from 'clis/slock/task-statuses.js';
const status = String(rawStatus ?? '').trim();
if (!TASK_STATUSES.includes(status)) {
  throw new Error(`status must be one of: ${TASK_STATUSES.join('|')}`);
}

Type guard

const isValidStatus = (s) => TASK_STATUSES.includes(String(s ?? '').trim());

Prevention

When it happens

Trigger: Running `slock task-status --taskId <id> --status <value>` where value (trimmed) is not an exact TASK_STATUSES member — bad casing, aliases like 'wip'/'done', or an empty/unset variable producing ''.

Common situations: Typing shorthand statuses; scripts interpolating free-form user input; migration from another tracker's vocabulary; casing changes after the status set was renamed.

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/8f0f33e1a6c4af25. Report an issue: GitHub.