{"record":{"id":"d9608e180dac79e7","repo":"JuliusBrussee/caveman","slug":"artifact-id-is-required","errorCode":null,"errorMessage":"artifact_id is required","messagePattern":"artifact_id is required","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"packages/sdk/python/caveman_cloud/core.py","lineNumber":1217,"sourceCode":"                \"x-cave-artifact-source\": str(options[\"source\"]),\n            },\n        )\n        if response.get(\"stored\") is False:\n            return value\n        if response.get(\"stored\") is not True or not isinstance(response.get(\"artifact_id\"), str) or not response[\"artifact_id\"].strip():\n            raise RuntimeError(\"cave artifact response missing artifact_id\")\n        content_type = options.get(\"content_type\") or \"application/json\"\n        return (\n            f\"[cave-artifact id={response['artifact_id']} source={options['source']} type={content_type}]\\n\"\n            f\"summary: artifact stored.\\n\"\n            f\"retrieve: authenticated GET /sdk/v1/artifacts/{response['artifact_id']} or trace.artifacts.get(\\\"{response['artifact_id']}\\\").\\n\"\n            f\"[/cave-artifact]\"\n        )\n\n    def get_artifact(self, artifact_id: str) -> Any:\n        \"\"\"Retrieve and JSON-decode one artifact produced by :meth:`page_artifact`.\"\"\"\n        if not isinstance(artifact_id, str) or not artifact_id.strip():\n            raise ValueError(\"artifact_id is required\")\n        return self._get(f\"/sdk/v1/artifacts/{quote(artifact_id, safe='')}\")\n\n    @property\n    def artifacts(self) -> \"_Artifacts\":\n        \"\"\"Artifact handle mirroring the TS ``trace.artifacts`` namespace.\n\n        ``trace.artifacts.page(value, options)`` is the mirror-named entry point\n        for :meth:`page_artifact`; both hit ``/sdk/v1/artifacts`` with an\n        identical wire body.\n        \"\"\"\n        return _Artifacts(self)\n\n    def checkpoint(self, messages: list[Any], options: dict[str, Any]) -> dict[str, Any]:\n        # `workflow` mirrors the TS SDK body; the gateway's checkpoint.Request\n        # carries it (omitempty) so both SDKs send the identical wire.\n        return self._request(\"/sdk/v1/checkpoints\", {\"messages\": messages, \"options\": options, \"workflow\": self.workflow})\n\n    def expand(self, source_ref: str) -> dict[str, Any]:","sourceCodeStart":1199,"sourceCodeEnd":1235,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/sdk/python/caveman_cloud/core.py#L1199-L1235","documentation":"`get_artifact()` requires a non-empty string `artifact_id` because it is interpolated into the authenticated GET path `/sdk/v1/artifacts/{id}`. Passing `None`, a non-string, or a whitespace-only string is rejected before any request; the id is URL-quoted with no safe characters when the call is made.","triggerScenarios":"Calling `client.get_artifact(\"\")`, `client.get_artifact(None)`, or passing an id extracted from a malformed artifact reference (e.g. the [cave-artifact id=...] block parsed incorrectly).","commonSituations":"A regex over the artifact reference block capturing the wrong group, a previous `page_artifact` failure leaving the id unset, or downstream code storing the id in an optional field that defaulted to empty.","solutions":["Pass the exact `artifact_id` string returned by `page_artifact` (from the [cave-artifact id=...] reference).","Guard before calling: `if isinstance(aid, str) and aid.strip(): client.get_artifact(aid)`.","Fix the parsing/extraction code that produced the empty id — parse the reference block rather than splitting on whitespace."],"exampleFix":"// before\nclient.get_artifact(ref.get(\"artifact_id\"))  # may be None/empty\n// after\naid = ref.get(\"artifact_id\")\nif isinstance(aid, str) and aid.strip():\n    client.get_artifact(aid)","handlingStrategy":"type-guard","validationCode":"aid = ref.get(\"artifact_id\")\nif isinstance(aid, str) and aid.strip():\n    artifact = client.get_artifact(aid)\nelse:\n    raise LookupError(f\"no usable artifact id in reference: {ref!r}\")","typeGuard":"def has_artifact_id(ref: object) -> TypeGuard[str]:\n    return isinstance(ref, str) and bool(ref.strip())","tryCatchPattern":"try:\n    artifact = client.get_artifact(aid)\nexcept ValueError as e:\n    log.error(\"get_artifact called with bad id %r: %s\", aid, e)","preventionTips":["Store the artifact_id string directly from page_artifact's structured return rather than re-parsing the display block.","Type artifact-id-bearing fields as non-optional in your dataclasses.","Write a regex test fixture covering the [cave-artifact id=...] format you parse."],"tags":["python","validation","artifacts","missing-argument"],"backgroundTag":"empty-required-field","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}