{"record":{"id":"ccd3481b4f9cefd7","repo":"shareAI-lab/learn-claude-code","slug":"args-changes-must-be-a-string","errorCode":null,"errorMessage":"args.changes must be a string","messagePattern":"args\\.changes must be a string","errorType":"validation","errorClass":"WorkflowInputError","httpStatus":null,"severity":"error","filePath":"s16_workflow_runtime/code.py","lineNumber":679,"sourceCode":"    \"description\": \"Review changed files across dimensions, verify each finding\",\n    \"phases\": [\"Review\", \"Verify\"],\n}\n\nDIMENSIONS = [\"correctness\", \"security\", \"performance\", \"style\"]\nDEMO_CHANGES = (\n    \"def load_user(user_id):\\n\"\n    \"    query = f\\\"SELECT * FROM users WHERE id = {user_id}\\\"\\n\"\n    \"    return db.execute(query).fetchone()\\n\"\n)\n\n\nasync def sample_workflow(ctx, args):\n    \"\"\"pipeline over review dimensions (audit -> verify-each), then keep only the\n    findings a verifier confirms. The plan is code, not a chat turn.\"\"\"\n    ctx.phase(\"Review\")\n    changes = args.get(\"changes\", \"\")\n    if not isinstance(changes, str):\n        raise WorkflowInputError(\"args.changes must be a string\")\n    review_input = changes.strip() or \"No change context was supplied.\"\n\n    async def audit(_value, dimension, _idx):\n        out = await ctx.agent(\n            f\"Review this change context for {dimension} issues. \"\n            \"Report only issues supported by the supplied text.\\n\\n\"\n            f\"{review_input}\",\n            schema=FINDINGS_SCHEMA, label=f\"audit:{dimension}\", phase=\"Review\")\n        return {\"dimension\": dimension, \"findings\": out[\"findings\"]}\n\n    async def verify(audited, dimension, _idx):\n        ctx.phase(\"Verify\")\n        # Each finding is verified by its own adversarial subagent, concurrently.\n        verdicts = await ctx.parallel([\n            (lambda f=f: ctx.agent(\n                f\"Adversarially verify this {dimension} finding against the \"\n                \"supplied change context.\\n\\n\"\n                f\"Change context:\\n{review_input}\\n\\n\"","sourceCodeStart":661,"sourceCodeEnd":697,"githubUrl":"https://github.com/shareAI-lab/learn-claude-code/blob/985456f4adea6f4df8fbad4112245dbd97444eae/s16_workflow_runtime/code.py#L661-L697","documentation":"The built-in sample_workflow validates its primary input: args.get('changes', '') must be a string, otherwise WorkflowInputError('args.changes must be a string') is raised before any agent runs. This is the workflow's own contract check — the runtime itself only requires args to be an object, so each workflow validates the shape of its specific fields.","triggerScenarios":"run_workflow('review', args={'changes': [list of diffs]}) or {'changes': 123} — any non-string value under the 'changes' key.","commonSituations":"Passing a diff/PR object or list of file changes instead of their serialized text; JSON clients coercing values; forgetting to stringify before calling the tool.","solutions":["Serialize the change context to a single string (join diffs, read file contents) before calling.","Omit 'changes' entirely to fall back to the default empty string ('No change context was supplied.').","Add the same isinstance check on the caller side before invoking the workflow."],"exampleFix":"# before\nargs = {\"changes\": [diff_a, diff_b]}\n\n# after\nargs = {\"changes\": \"\\n\\n\".join([diff_a, diff_b])}","handlingStrategy":"type-guard","validationCode":"changes = args.get(\"changes\", \"\")\nif not isinstance(changes, str):\n    args = {**args, \"changes\": \"\\n\".join(map(str, changes['changes'] if isinstance(changes, list) else [changes]))}\nawait run_workflow(\"review\", args=args)","typeGuard":"def valid_review_args(args) -> bool:\n    return isinstance(args, dict) and isinstance(args.get(\"changes\", \"\"), str)","tryCatchPattern":"try:\n    await run_workflow(\"review\", args=args)\nexcept WorkflowInputError as e:\n    if \"args.changes must be a string\" in str(e):\n        args[\"changes\"] = str(args[\"changes\"])\n        return await run_workflow(\"review\", args=args)\n    raise","preventionTips":["Stringify diffs/change context at the boundary (join lists, read files) before building args.","Mirror each workflow's arg contract with a client-side schema check.","Omit optional keys rather than sending nulls; defaults (e.g. changes='') are handled by the workflow."],"tags":["workflow","args","type-validation","input-contract"],"backgroundTag":null,"analyzedSha":"985456f4adea6f4df8fbad4112245dbd97444eae","analyzedAt":"2026-08-14T22:02:26.028Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}