{"record":{"id":"4562a73727ab600c","repo":"Hmbown/CodeWhale","slug":"unit-must-be-chars-or-lines","errorCode":null,"errorMessage":"unit must be 'chars' or 'lines'","messagePattern":"unit must be 'chars' or 'lines'","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"crates/tui/src/repl/runtime.rs","lineNumber":865,"sourceCode":"\ndef _slice_chars(start, end):\n    total = len(_context)\n    s = max(0, int(start))\n    e = max(s, min(total, int(end)))\n    return _context[s:e]\n\ndef _slice_lines(start, end):\n    lines = _context.splitlines()\n    s = max(0, int(start))\n    e = max(s, min(len(lines), int(end)))\n    return \"\\n\".join(lines[s:e])\n\ndef peek(start, end, unit=\"chars\"):\n    \"\"\"Return a bounded slice of the input by char offsets or line numbers.\"\"\"\n    if str(unit).lower() in (\"line\", \"lines\"):\n        return _slice_lines(start, end)\n    if str(unit).lower() not in (\"char\", \"chars\"):\n        raise ValueError(\"unit must be 'chars' or 'lines'\")\n    return _slice_chars(start, end)\n\ndef search(pattern, max_hits=100):\n    \"\"\"Regex-search the input and return bounded hit records with snippets.\"\"\"\n    max_hits = max(0, int(max_hits))\n    hits = []\n    if max_hits == 0:\n        return hits\n    rx = _re.compile(str(pattern), _re.MULTILINE)\n    for i, m in enumerate(rx.finditer(_context)):\n        if i >= max_hits:\n            break\n        start, end = m.span()\n        snippet_start = max(0, start - 120)\n        snippet_end = min(len(_context), end + 120)\n        hits.append({\n            \"index\": i,\n            \"start\": start,","sourceCodeStart":847,"sourceCodeEnd":883,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/8880682c63083a91624de936797efa3ce9e498fd/crates/tui/src/repl/runtime.rs#L847-L883","documentation":"Raised by peek(start, end, unit) inside the Python REPL the Codewhale TUI embeds (crates/tui/src/repl/runtime.rs) for model-generated code operating on the loaded input _context. The unit argument accepts only 'char'/'chars' or 'line'/'lines' after str().lower(); anything else raises ValueError before any slicing happens. It guards the bounded slicer against requests for units it cannot serve, such as words or tokens.","triggerScenarios":"Model-generated REPL code calling peek(0, 200, 'words'), peek(0, 10, 'tokens'), peek(0, 3, 'sentences'), or passing a non-string like ['chars'] whose str() form no longer matches the closed set.","commonSituations":"LLMs assuming token-based offsets from other APIs; prompts instructing sentence-level peeks; code migrated from tools whose peek supports arbitrary units.","solutions":["Use unit='chars' (the default) or unit='lines'","For finer granularity, call search() with a regex and work with the returned snippets","When generating code for this REPL, treat the error text as the closed vocabulary and retry with a supported unit","For word-level access, slice lines yourself: peek(0, n, 'lines').split()"],"exampleFix":"# before\npeek(0, 40, 'words')\n# after\npeek(0, 40, 'chars')  # or peek(0, 5, 'lines')","handlingStrategy":"type-guard","validationCode":"unit = 'lines' if want_line_numbers else 'chars'\ntext = peek(start, end, unit)","typeGuard":"def normalize_unit(u, default='chars'):\n    u = str(u).lower()\n    if u in ('line', 'lines'):\n        return 'lines'\n    if u in ('char', 'chars'):\n        return 'chars'\n    return default\n\ntext = peek(start, end, normalize_unit(unit))","tryCatchPattern":"try:\n    text = peek(start, end, unit)\nexcept ValueError:\n    text = peek(start, end, 'chars')","preventionTips":["Whitelist units through a normalize helper before every peek call","Enumerate supported units in prompts that generate REPL code","Use search() with a regex for sub-line granularity"],"tags":["python","repl","validation","llm","slicing"],"backgroundTag":null,"analyzedSha":"8880682c63083a91624de936797efa3ce9e498fd","analyzedAt":"2026-08-16T11:31:27.956Z","schemaVersion":2},"datasetVersion":"2026-08-16T13:17:31.715Z"}