{"record":{"id":"8f0f33e1a6c4af25","repo":"jackwener/OpenCLI","slug":"status-status-not-in-task-statuses-join-8f0f33","errorCode":null,"errorMessage":"status \"${status}\" not in {${TASK_STATUSES.join('|')}} — pre-network reject (saves a 400 round-trip).","messagePattern":"status \"(.+?)\" not in (.+?) — pre-network reject \\(saves a 400 round-trip\\)\\.","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/slock/task-status.js","lineNumber":41,"sourceCode":"  access: 'write',\n  description: `Set a task's status (PATCH /tasks/:taskId/status, body {status}). One of ${TASK_STATUSES.join('|')}.`,\n  domain: SLOCK_DOMAIN,\n  strategy: Strategy.COOKIE,\n  browser: true,\n  siteSession: 'persistent',\n  args: [\n    { name: 'taskId', positional: true, required: true, help: 'Full task UUID (= message id; short ids rejected)' },\n    { name: 'status', positional: true, required: true, help: `One of: ${TASK_STATUSES.join('|')}` },\n    { name: 'server', help: 'Override active server' },\n  ],\n  columns: ['taskId', 'taskStatus', 'assigneeId', 'taskNumber'],\n  func: async (page, kwargs) => {\n    let id;\n    try { id = assertMessageIdShape(String(kwargs.taskId ?? '')); }\n    catch (e) { throw new ArgumentError(e.message); }\n    const status = String(kwargs.status ?? '').trim();\n    if (!TASK_STATUSES.includes(status)) {\n      throw new ArgumentError(`status \"${status}\" not in {${TASK_STATUSES.join('|')}} — pre-network reject (saves a 400 round-trip).`);\n    }\n    await page.goto(SLOCK_HOME_URL);\n    const snippet = `\n      ${authHeadersFragment({ serverScoped: true, serverIdOverride: kwargs.server })}\n      const res = await fetch('${SLOCK_API_BASE}/tasks/' + encodeURIComponent(${JSON.stringify(id)}) + '/status', {\n        method:'PATCH', credentials:'include', headers,\n        body: JSON.stringify({ status: ${JSON.stringify(status)} }),\n      });\n      if (res.status === 400) {\n        const j = await res.json().catch(() => ({}));\n        return { kind: 'http', status: 400, where: '/tasks/:taskId/status (bad request: ' + (j.error || j.message || 'invalid status transition') + ')' };\n      }\n      if (res.status === 403) return { kind: 'http', status: 403, where: '/tasks/:taskId/status (forbidden — terminal status (done/closed), not the assignee, or channel archived)' };\n      if (res.status === 404) return { kind: 'http', status: 404, where: '/tasks/:taskId/status (task not found)' };\n      // F6 — actionable hint for repeat-set 409 (\"status already X\").\n      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)' };\n      if (!res.ok) return { kind: res.status===401?'auth':'http', status: res.status, where:'/tasks/:taskId/status' };\n      const data = await res.json().catch(() => ({}));","sourceCodeStart":23,"sourceCodeEnd":59,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/slock/task-status.js#L23-L59","documentation":"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.","triggerScenarios":"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 ''.","commonSituations":"Typing shorthand statuses; scripts interpolating free-form user input; migration from another tracker's vocabulary; casing changes after the status set was renamed.","solutions":["Run `slock task-status --help` and pass exactly one of the listed statuses","Normalize casing/spelling to the canonical tokens (e.g. 'done' → whatever TASK_STATUSES defines)","Validate the value in your wrapper before invoking to give users a friendlier message","Present a picker/autocomplete of TASK_STATUSES instead of free-text input"],"exampleFix":"// before\nawait cli('slock', 'task-status', '--taskId', id, '--status', 'Done');\n// after\nawait cli('slock', 'task-status', '--taskId', id, '--status', 'done'); // exact member","handlingStrategy":"validation","validationCode":"import { TASK_STATUSES } from 'clis/slock/task-statuses.js';\nconst status = String(rawStatus ?? '').trim();\nif (!TASK_STATUSES.includes(status)) {\n  throw new Error(`status must be one of: ${TASK_STATUSES.join('|')}`);\n}","typeGuard":"const isValidStatus = (s) => TASK_STATUSES.includes(String(s ?? '').trim());","tryCatchPattern":null,"preventionTips":["Only pass statuses listed in --help","Offer a fixed-choice picker instead of free text in tooling","Normalize casing/spelling to canonical tokens before invoking"],"tags":["argument-validation","enum-validation","fail-fast"],"backgroundTag":"invalid-enum-value","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}