{"record":{"id":"3cd498ee115d336f","repo":"langflow-ai/langflow","slug":"actions-must-be-strings","errorCode":null,"errorMessage":"actions must be strings","messagePattern":"actions must be strings","errorType":"validation","errorClass":"ValueError","httpStatus":422,"severity":"warning","filePath":"src/backend/base/langflow/api/v1/authz_me.py","lineNumber":104,"sourceCode":"    @classmethod\n    def _normalize_actions(cls, value: list[str] | None) -> list[str] | None:\n        \"\"\"Lowercase, strip, de-duplicate (order-preserving), and cap the actions list.\n\n        Returns ``None`` when the caller omitted the field (handler substitutes\n        ``_DEFAULT_ACTIONS``). An empty list after normalization also returns\n        ``None`` so the default kicks in rather than producing an empty cartesian\n        product downstream.\n        \"\"\"\n        if value is None:\n            return None\n        seen: set[str] = set()\n        normalized: list[str] = []\n        for raw in value:\n            if not isinstance(raw, str):\n                # Pydantic field_validators must raise ValueError (not TypeError)\n                # to be wrapped into a ValidationError -> HTTP 422 response.\n                msg = \"actions must be strings\"\n                raise ValueError(msg)  # noqa: TRY004\n            cleaned = raw.strip().lower()\n            if not cleaned or cleaned in seen:\n                continue\n            seen.add(cleaned)\n            normalized.append(cleaned)\n        if len(normalized) > _MAX_ACTIONS:\n            msg = f\"actions capped at {_MAX_ACTIONS} unique entries\"\n            raise ValueError(msg)\n        return normalized or None\n\n\nclass EffectivePermissionsResponse(BaseModel):\n    \"\"\"Response: ``{resource_id: [allowed_actions]}``.\"\"\"\n\n    resource_type: ResourceTypeLiteral\n    permissions: dict[UUID, list[str]]\n\n","sourceCodeStart":86,"sourceCodeEnd":122,"githubUrl":"https://github.com/langflow-ai/langflow/blob/976ec789d2886a86de109c044d089d68e96c9a35/src/backend/base/langflow/api/v1/authz_me.py#L86-L122","documentation":"Pydantic field-validator error on the /authz/me effective-permissions request body: every element of the actions array must be a Python str. Raising ValueError inside the validator makes Pydantic wrap it into a ValidationError, which FastAPI returns as HTTP 422.","triggerScenarios":"POST to the effective-permissions endpoint with actions containing a non-string, e.g. {\"actions\": [null, 123, true, {}]} or actions sent as a nested array [[\"read\"]] instead of a flat list of strings.","commonSituations":"JS clients pushing undefined/null entries into the array via array.push(someVar) where someVar is undefined, JSON serializers emitting numbers for action ids, or a schema change where actions was previously an enum/objects list.","solutions":["Send actions as a flat JSON array of strings, e.g. [\"read\",\"write\",\"execute\"]","Filter falsy entries client-side before sending: actions.filter(Boolean)","Lowercase and trim values — the validator normalizes case/whitespace but the type must already be string","Omit actions entirely to use the default action set"],"exampleFix":"// before\nbody: JSON.stringify({ resource_type: 'flow', resource_ids: ids, actions: [null, 'read'] })\n\n// after\nbody: JSON.stringify({ resource_type: 'flow', resource_ids: ids, actions: ['read'] })","handlingStrategy":"type-guard","validationCode":"const actions = rawActions.filter(a => typeof a === 'string' && a.trim());","typeGuard":"const isStringArray = (v: unknown): v is string[] =>\n  Array.isArray(v) && v.every(x => typeof x === 'string');","tryCatchPattern":"catch (e) { if (e.status === 422) showFormErrors(e.errors); } // map pydantic errors to form fields","preventionTips":["Type the request body in TypeScript so non-strings fail at compile time","Filter undefined/null out of arrays built from optional variables","Keep actions as a flat string list — no nested arrays or objects"],"tags":["authz","pydantic","validation","http-422"],"backgroundTag":null,"analyzedSha":"976ec789d2886a86de109c044d089d68e96c9a35","analyzedAt":"2026-08-14T18:23:12.227Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}