{"record":{"id":"cbb8eafa1d91d3dc","repo":"coleam00/Archon","slug":"label-must-be-a-json-encoded-object","errorCode":null,"errorMessage":"${label} must be a JSON-encoded object","messagePattern":"(.+?) must be a JSON-encoded object","errorType":"http","errorClass":null,"httpStatus":400,"severity":"warning","filePath":"packages/server/src/routes/api.ts","lineNumber":3440,"sourceCode":"        }\n        let decoded: unknown;\n        try {\n          decoded = JSON.parse(rawInputs);\n        } catch (parseErr: unknown) {\n          getLog().warn({ err: parseErr, workflowName }, 'run_workflow.inputs_parse_failed');\n          return apiError(c, 400, 'inputs must be a JSON-encoded object of string values');\n        }\n        const parsed = parseRunInputsField(decoded);\n        if (!parsed.ok) return apiError(c, 400, parsed.error);\n        workflowInputs = parsed.inputs;\n      }\n\n      const decodeObjectField = (\n        raw: string | File | (string | File)[] | undefined,\n        label: 'tiers' | 'aliases'\n      ): { ok: true; value?: unknown } | { ok: false; error: string } => {\n        if (raw === undefined) return { ok: true };\n        if (typeof raw !== 'string') {\n          return { ok: false, error: `${label} must be a JSON-encoded object` };\n        }\n        try {\n          return { ok: true, value: JSON.parse(raw) as unknown };\n        } catch (parseErr: unknown) {\n          getLog().warn(\n            { err: parseErr, workflowName, field: label },\n            'run_workflow.model_overrides_parse_failed'\n          );\n          return { ok: false, error: `${label} must be a JSON-encoded object` };\n        }\n      };\n      const decodedTiers = decodeObjectField(body.tiers, 'tiers');\n      if (!decodedTiers.ok) return apiError(c, 400, decodedTiers.error);\n      const decodedAliases = decodeObjectField(body.aliases, 'aliases');\n      if (!decodedAliases.ok) return apiError(c, 400, decodedAliases.error);\n      const parsedOverrides = parseRunModelOverridesFields(\n        decodedTiers.value,","sourceCodeStart":3422,"sourceCodeEnd":3458,"githubUrl":"https://github.com/coleam00/Archon/blob/0773b9745896ef0612e709c80845a0f7db315b19/packages/server/src/routes/api.ts#L3422-L3458","documentation":"The run-workflow route decodes multipart 'tiers' and 'aliases' fields via decodeObjectField; each must be absent or a JSON-encoded object string. If the field is present but is not a string (e.g. an uploaded File or an array of parts from { all: true }), the route returns 400 with '<label> must be a JSON-encoded object' before even attempting JSON.parse.","triggerScenarios":"Sending tiers or aliases as a file part (name=\"tiers\"; filename=...) instead of a plain text form field, or duplicating the field so parseBody({ all: true }) yields an array, or sending a non-string part type.","commonSituations":"Uploading a tiers.json file via -F 'tiers=@tiers.json' instead of -F 'tiers=<json>'; accidentally appending the same field twice; generic form builders that attach files for object payloads.","solutions":["Send tiers/aliases as plain text fields containing JSON: -F 'tiers={\"default\":2}'.","Send each field exactly once — no duplicate parts.","Do not attach these fields as files; the route accepts strings only.","If you have them in files, read and inline the contents as the field value."],"exampleFix":"// before\n-F 'tiers=@tiers.json'   // arrives as a File part\n// after\n-F \"tiers=$(cat tiers.json)\"   // arrives as a string part","handlingStrategy":"validation","validationCode":"function appendJsonField(form: FormData, label: 'tiers' | 'aliases', value: Record<string, unknown>): void {\n  form.append(label, JSON.stringify(value)); // plain string part, not a file, appended once\n}\n// pre-send check:\nconst raw = form.get('tiers');\nif (raw !== null && (typeof raw !== 'string' || !isValidJsonObject(raw))) throw new Error('tiers must be a JSON-encoded object string');","typeGuard":"function isJsonEncodedObject(s: unknown): s is string {\n  if (typeof s !== 'string') return false;\n  try {\n    const v: unknown = JSON.parse(s);\n    return typeof v === 'object' && v !== null && !Array.isArray(v);\n  } catch { return false; }\n}","tryCatchPattern":"try {\n  const res = await runWorkflow(form);\n  if (res.status === 400 && (await res.json()).error.includes('must be a JSON-encoded object')) {\n    throw new Error('tiers/aliases must be plain text fields containing a JSON object — not file uploads, not duplicates');\n  }\n} catch (err) { /* rebuild form fields as strings */ }","preventionTips":["Append tiers/aliases as text fields (form.append(name, jsonString)), never with a filename/file blob.","Append each field exactly once; duplicate parts parse into arrays and fail the string check.","Lint the JSON with jq before sending.","Inline file contents into the field value when the data lives in a file."],"tags":["http-400","json","workflow","input-validation"],"backgroundTag":"invalid-json-field","analyzedSha":"0773b9745896ef0612e709c80845a0f7db315b19","analyzedAt":"2026-09-01T02:28:07.064Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}