{"record":{"id":"245cfcae9d5bc40a","repo":"infiniflow/ragflow","slug":"101-245cfc","errorCode":"101","errorMessage":"Invalid DSL JSON string.","messagePattern":"Invalid DSL JSON string\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"api/apps/services/canvas_replica_service.py","lineNumber":53,"sourceCode":"    \"\"\"\n\n    TTL_SECS = 3 * 60 * 60\n    REPLICA_KEY_PREFIX = \"canvas:replica\"\n    LOCK_KEY_PREFIX = \"canvas:replica:lock\"\n    LOCK_TIMEOUT_SECS = 10\n    LOCK_BLOCKING_TIMEOUT_SECS = 1\n    LOCK_RETRY_ATTEMPTS = 3\n    LOCK_RETRY_SLEEP_SECS = 0.2\n\n    @classmethod\n    def normalize_dsl(cls, dsl):\n        \"\"\"Normalize DSL to a JSON-serializable dict. Raise ValueError on invalid input.\"\"\"\n        normalized = dsl\n        if isinstance(normalized, str):\n            try:\n                normalized = json.loads(normalized)\n            except Exception as e:\n                raise ValueError(\"Invalid DSL JSON string.\") from e\n\n        if not isinstance(normalized, dict):\n            raise ValueError(\"DSL must be a JSON object.\")\n\n        try:\n            return json.loads(json.dumps(normalize_chunker_dsl(normalized), ensure_ascii=False))\n        except Exception as e:\n            raise ValueError(\"DSL is not JSON-serializable.\") from e\n\n    @classmethod\n    def _replica_key(cls, canvas_id: str, tenant_id: str, runtime_user_id: str) -> str:\n        return f\"{cls.REPLICA_KEY_PREFIX}:{canvas_id}:{tenant_id}:{runtime_user_id}\"\n\n    @classmethod\n    def _lock_key(cls, canvas_id: str, tenant_id: str, runtime_user_id: str) -> str:\n        return f\"{cls.LOCK_KEY_PREFIX}:{canvas_id}:{tenant_id}:{runtime_user_id}\"\n\n    @classmethod","sourceCodeStart":35,"sourceCodeEnd":71,"githubUrl":"https://github.com/infiniflow/ragflow/blob/554fb1133ac3861732235ad9c377eb5e0a770665/api/apps/services/canvas_replica_service.py#L35-L71","documentation":"Raised by CanvasReplicaService.normalize_dsl when the canvas DSL is provided as a string but json.loads fails to parse it. The replica service stores per-user canvas runtime copies in Redis and requires the DSL to be a plain JSON object, so any malformed JSON string (trailing commas, single quotes, truncated payloads, BOM) is rejected with ValueError before touching Redis.","triggerScenarios":"Calling a canvas run/save API with dsl as a string containing invalid JSON — e.g. Python-repr dicts with single quotes, truncated request bodies, or strings produced by str(dict) instead of json.dumps. Reached via _read_payload (corrupt Redis replica) or _build_payload (bad input DSL).","commonSituations":"Serializing a dict with str() or repr() instead of json.dumps(); hand-editing canvas DSL strings; truncated HTTP bodies behind proxies; a corrupted Redis replica entry (in which case _read_payload logs a warning and returns None instead).","solutions":["Serialize the DSL with json.dumps(dsl) (or pass the dict directly) before sending","Validate the string locally with json.loads() before the API call","If the string came out of Redis, flush the canvas:replica:* key so a fresh replica is bootstrapped from the DB DSL","Check for truncated payloads: log len(dsl_string) and compare with what the client sent"],"exampleFix":"# before\ndsl_str = str(canvas_dsl)          # single quotes -> invalid JSON\nservice.normalize_dsl(dsl_str)\n\n# after\nimport json\ndsl_str = json.dumps(canvas_dsl)   # valid JSON\nservice.normalize_dsl(dsl_str)","handlingStrategy":"validation","validationCode":"import json\n\ndef valid_dsl_json(dsl_str: str) -> bool:\n    try:\n        json.loads(dsl_str)\n        return True\n    except (json.JSONDecodeError, TypeError):\n        return False","typeGuard":"def is_json_string(v) -> bool:\n    return isinstance(v, str) and _valid_dsl_json(v)","tryCatchPattern":"try:\n    CanvasReplicaService.normalize_dsl(dsl)\nexcept ValueError as e:\n    # message names the exact stage: invalid JSON / not an object / not serializable\n    logger.warning(\"DSL rejected: %s\", e)","preventionTips":["Always build DSL strings with json.dumps, never str() or repr()","Pass dicts directly when the API accepts objects","Round-trip test locally: json.loads(json.dumps(dsl)) before sending"],"tags":["canvas","dsl","json","validation"],"backgroundTag":null,"analyzedSha":"554fb1133ac3861732235ad9c377eb5e0a770665","analyzedAt":"2026-08-15T09:20:16.380Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}