{"record":{"id":"3f9611cef1339bd8","repo":"usestrix/strix","slug":"spec-has-a-control-character-in-its-destinatio","errorCode":null,"errorMessage":"'{spec}' has a control character in its destination path","messagePattern":"'(.+?)' has a control character in its destination path","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"strix/interface/utils.py","lineNumber":1711,"sourceCode":"    \"\"\"Return the workspace-relative destination declared by ``spec``.\"\"\"\n    _, sep, dest = spec.rpartition(\":\")\n    candidate = dest.strip() if sep and dest.strip() else source.name\n    if candidate.startswith(\"/\") or Path(candidate).is_absolute():\n        if not candidate.startswith(\"/workspace/\"):\n            raise ValueError(\n                f\"'{spec}' must land inside the workspace: use a relative \"\n                \"destination or a path under /workspace\"\n            )\n        candidate = candidate.removeprefix(\"/workspace/\")\n    candidate = candidate.strip(\"/\")\n    if not candidate:\n        raise ValueError(f\"'{spec}' has an empty destination path\")\n    if any(part in (\"\", \".\", \"..\") for part in candidate.split(\"/\")):\n        raise ValueError(f\"'{spec}' has an invalid destination path: {candidate}\")\n    # A control character would let the path span more than the one line it is\n    # rendered on in the agent task, so the whole spec is rejected.\n    if any(ord(char) < 0x20 or ord(char) == 0x7F for char in candidate):\n        raise ValueError(f\"'{spec}' has a control character in its destination path\")\n    return candidate\n\n\ndef resolve_workspace_files(specs: list[str] | None) -> list[dict[str, str]]:\n    \"\"\"Validate ``PATH[:DEST]`` specs into source/destination pairs.\n\n    Each spec names a readable host file. ``DEST`` is the path inside\n    ``/workspace``; it defaults to the file name. Raises ``ValueError`` with a\n    user-facing message when a spec is unusable.\n    \"\"\"\n    resolved: list[dict[str, str]] = []\n    seen: dict[str, str] = {}\n    for spec in specs or []:\n        raw, sep, dest = spec.rpartition(\":\")\n        source_text = raw if sep and dest.strip() else spec\n        source = Path(source_text.strip()).expanduser()\n        if not source.is_file():\n            raise ValueError(f\"'{source}' is not an existing file\")","sourceCodeStart":1693,"sourceCodeEnd":1729,"githubUrl":"https://github.com/usestrix/strix/blob/85513391305171ecc6faffe03da4a8bda5e3febb/strix/interface/utils.py#L1693-L1729","documentation":"Raised by _workspace_file_dest() when the destination path contains a control character (any char with ord < 0x20, or 0x7F DEL). The destination is rendered inline in the agent task text, and a control character could make the path span more than its single rendered line (or smuggle line breaks), so the whole spec is rejected defensively.","triggerScenarios":"Passing a spec whose DEST contains a newline, tab, escape sequence, or raw byte like \\x01: --workspace-file a.txt:bad\\nname, or specs read from a file/JSON with embedded escape codes (\\r\\n line endings) that were never stripped.","commonSituations":"Specs generated from untrusted or machine-written data (CSV, JSON payloads) containing \\r or \\n; a terminal paste accident injecting a raw control byte; CI config files with Windows CRLF line endings bleeding into a variable used as DEST.","solutions":["Strip/validate control characters before passing specs: ''.join(c for c in dest if ord(c) >= 0x20 and ord(c) != 0x7F)","Fix the upstream data: strip \\r\\n from lines read from spec files: line.rstrip('\\r\\n')","Type the destination manually as plain ASCII instead of pasting from formatted text"],"exampleFix":"# before\nspecs = [line for line in open('specs.txt')]  # may carry trailing \\r\\nstrix --workspace-file specs[0] ...\n# after\nspecs = [line.rstrip('\\r\\n') for line in open('specs.txt')]\nstrix --workspace-file specs[0] ...","handlingStrategy":"validation","validationCode":"def has_control_chars(s: str) -> bool:\n    return any(ord(c) < 0x20 or ord(c) == 0x7F for c in s)\n\nspecs = [s for s in raw_specs if not has_control_chars(s)]","typeGuard":null,"tryCatchPattern":"try:\n    resolve_workspace_files(specs)\nexcept ValueError as e:\n    if 'control character' in str(e):\n        specs = [clean(s) for s in specs]  # strip and re-run\n    else:\n        raise","preventionTips":["Strip line endings when reading specs from files: line.rstrip('\\r\\n')","Avoid pasting destinations from rich-text/terminal output","Sanitize machine-generated paths before passing them as specs"],"tags":["strix","cli","sanitization","workspace-files"],"backgroundTag":null,"analyzedSha":"85513391305171ecc6faffe03da4a8bda5e3febb","analyzedAt":"2026-08-15T05:03:57.275Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}