{"record":{"id":"95661da48a3c1f69","repo":"abhigyanpatwari/GitNexus","slug":"transcript-artifact-metadata-is-malformed","errorCode":null,"errorMessage":"transcript artifact metadata is malformed","messagePattern":"transcript artifact metadata is malformed","errorType":"exception","errorClass":"SandboxError","httpStatus":null,"severity":"error","filePath":"eval/workflow_bench/evolve.py","lineNumber":336,"sourceCode":"        if stat.S_ISLNK(metadata.st_mode) or not stat.S_ISDIR(metadata.st_mode):\n            raise SandboxError(f\"results artifact parent must be a real directory: {current}\")\n        if transcript and stat.S_IMODE(metadata.st_mode) & 0o077:\n            raise SandboxError(f\"transcript artifact parent must be owner-only: {current}\")\n    return root / Path(*relative.parts)\n\n\ndef _transcript_artifact_metadata(metadata: Any) -> tuple[str, str, int]:\n    \"\"\"Validate transcript metadata without touching any host path.\"\"\"\n\n    if not isinstance(metadata, dict) or set(metadata) != {\"path\", \"sha256\", \"bytes\", \"source\"}:\n        raise SandboxError(\"transcript artifact metadata must contain only path, sha256, bytes, and source\")\n    relative = metadata[\"path\"]\n    expected_digest = metadata[\"sha256\"]\n    expected_size = metadata[\"bytes\"]\n    if metadata[\"source\"] != runner_sessions.PARENT_EVENT_STREAM_SOURCE:\n        raise SandboxError(\"transcript artifact source is not the parent event stream\")\n    if not isinstance(relative, str) or not re.fullmatch(r\"[0-9a-f]{64}\", str(expected_digest)):\n        raise SandboxError(\"transcript artifact metadata is malformed\")\n    if not isinstance(expected_size, int) or isinstance(expected_size, bool):\n        raise SandboxError(\"transcript artifact byte count must be an integer\")\n    if expected_size < 0 or expected_size > runner.MAX_TRANSCRIPT_BYTES:\n        raise SandboxError(\"transcript artifact exceeds the bounded run-output limit\")\n    return relative, expected_digest, expected_size\n\n\ndef _normalized_transcript_artifact_path(relative_value: str) -> str:\n    \"\"\"Apply the transcript path contract without touching the filesystem.\"\"\"\n\n    relative = PurePosixPath(relative_value)\n    if (\n        relative.is_absolute()\n        or len(relative.parts) != 2\n        or relative.parts[0] != \"transcripts\"\n        or any(part in {\"\", \".\", \"..\"} for part in relative.parts)\n    ):\n        raise SandboxError(f\"unsafe results artifact path: {relative_value!r}\")","sourceCodeStart":318,"sourceCodeEnd":354,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/d540b00184d71a896261ee02670da9a92d59d8f7/eval/workflow_bench/evolve.py#L318-L354","documentation":"After source is validated, the path must be a str and the sha256 must match the regex [0-9a-f]{64} (lowercase hex, exactly 64 chars). A non-string path, an uppercase-hex digest, a sha1 (40 chars), or a non-hex digest is treated as malformed metadata and rejected before any file is opened.","triggerScenarios":"path is None/a list/a Path object; sha256 is uppercase ('ABCDEF...'), a sha1 (40 chars), a sha256 with dashes, or contains non-hex characters; a base64 digest was recorded by mistake.","commonSituations":"A runner that uppercased the digest; sha1 from a legacy path; a path stored as a structured value; truncation/copy errors shortening the digest.","solutions":["Normalize the digest to lowercase 64-char hex: `hashlib.sha256(data).hexdigest()` already yields this.","Ensure path is a plain str (call str() if it is a Path), matching the 'transcripts/<name>' contract.","Regenerate the row with the current runner so digest and path are written canonically."],"exampleFix":"# before\n{\"path\": \"transcripts/r.json\", \"sha256\": \"ABCDEF0123...\", \"bytes\": 12, \"source\": \"parent-captured-stream-json\"}\n\n# after\nimport hashlib\ndigest = hashlib.sha256(blob).hexdigest()  # lowercase, 64 hex\nrow = {\"path\": \"transcripts/r.json\", \"sha256\": digest, \"bytes\": 12, \"source\": PARENT_EVENT_STREAM_SOURCE}","handlingStrategy":"validation","validationCode":"import re\nfrom pathlib import PurePosixPath\n\nHEX64 = re.compile(r\"[0-9a-f]{64}\")\n\ndef transcript_path_and_digest_ok(metadata: dict) -> bool:\n    rel = metadata.get(\"path\")\n    digest = metadata.get(\"sha256\")\n    return isinstance(rel, str) and bool(HEX64.fullmatch(str(digest)))","typeGuard":"import re\nHEX64 = re.compile(r\"[0-9a-f]{64}\")\n\ndef is_valid_sha256_hex(value: object) -> bool:\n    return isinstance(value, str) and bool(HEX64.fullmatch(value))","tryCatchPattern":null,"preventionTips":["Always produce digests with hashlib.sha256(...).hexdigest() (lowercase, 64 hex).","Store paths as plain 'transcripts/<name>' strings.","Validate digest format at write time, not just read time."],"tags":["workflow-bench","sandbox","validation","transcript","hash","schema"],"backgroundTag":null,"analyzedSha":"d540b00184d71a896261ee02670da9a92d59d8f7","analyzedAt":"2026-08-12T19:50:25.132Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}