{"record":{"id":"7b3f5053a48484e4","repo":"can1357/oh-my-pi","slug":"replacement-text-is-not-valid-utf-8-err","errorCode":null,"errorMessage":"Replacement text is not valid UTF-8: {err}","messagePattern":"Replacement text is not valid UTF-8: (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/pi-ast/src/ops.rs","lineNumber":331,"sourceCode":"\tlet mut prev_end = 0usize;\n\tfor edit in &sorted {\n\t\tif edit.position < prev_end {\n\t\t\treturn Err(anyhow!(\n\t\t\t\t\"Overlapping replacements detected; refine pattern to avoid ambiguous edits\"\n\t\t\t));\n\t\t}\n\t\tprev_end = edit.position.saturating_add(edit.deleted_length);\n\t}\n\n\tlet mut output = content.to_string();\n\tfor edit in sorted.into_iter().rev() {\n\t\tlet start = edit.position;\n\t\tlet end = edit.position.saturating_add(edit.deleted_length);\n\t\tif end > output.len() || start > end {\n\t\t\treturn Err(anyhow!(\"Computed edit range is out of bounds\"));\n\t\t}\n\t\tlet replacement = std::str::from_utf8(&edit.inserted_text)\n\t\t\t.map_err(|err| anyhow!(\"Replacement text is not valid UTF-8: {err}\"))?;\n\t\toutput.replace_range(start..end, replacement);\n\t}\n\tOk(output)\n}\n\npub fn collect_matched_files(\n\tcwd: &Path,\n\tpatterns: &[String],\n) -> Result<Vec<MatchedFile>, std::io::Error> {\n\tlet globset = build_globset(patterns)?;\n\tlet mut builder = WalkBuilder::new(cwd);\n\tbuilder\n\t\t.hidden(false)\n\t\t.git_ignore(true)\n\t\t.git_global(true)\n\t\t.git_exclude(true);\n\tlet mut files = Vec::new();\n\tfor entry in builder.build() {","sourceCodeStart":313,"sourceCodeEnd":349,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/crates/pi-ast/src/ops.rs#L313-L349","documentation":"The stdout reader loop expects every non-empty line from the RPC server to be a JSON frame. A line that fails json.loads is wrapped in RpcError including the line number and a snippet (truncated to 240 chars) so you can see what the server actually printed.","triggerScenarios":"Server printing human-readable text, warnings, debug logs, or tracebacks to stdout instead of only JSON-RPC frames; stderr leaking into stdout via shell redirection; output corruption or mixed encodings.","commonSituations":"Wrapping the server with a shell wrapper that echoes text, an env var enabling verbose logging that writes to stdout, an incompatible server version emitting a different framing (e.g. Content-Length headers instead of line-delimited JSON).","solutions":["Look at the Frame snippet in the message to see the offending line — usually a log line or banner.","Configure the server to send all logs/diagnostics to stderr, keeping stdout JSON-only.","Remove shell wrappers/redirects (2>&1) that merge stderr into stdout.","Ensure server and client protocol versions match (line-delimited JSON framing).","Catch RpcError around client usage and log the snippet for diagnosis before retrying."],"exampleFix":"// before (spawning with merged output)\nproc = subprocess.Popen(cmd, stderr=subprocess.STDOUT)\n// after\nproc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)","handlingStrategy":"try-catch","validationCode":"# sanity-check the server binary emits line-delimited JSON before wiring the client\ntest_proc = subprocess.run([server_cmd, \"--version\"], capture_output=True)\nassert not looks_like_plain_text(test_proc.stdout), \"server prints non-JSON to stdout\"","typeGuard":"def is_json_line(line: str) -> bool:\n    try:\n        json.loads(line); return True\n    except json.JSONDecodeError:\n        return False","tryCatchPattern":"try:\n    result = await client.request(\"prompt\", payload)\nexcept RpcError as exc:\n    if \"Failed to decode RPC output\" in str(exc):\n        logger.error(\"server stdout contaminated with non-JSON; check the Frame snippet: %s\", exc)\n    raise","preventionTips":["Configure the server so all logs/banners go to stderr; stdout is JSON-only.","Never redirect stderr into stdout (2>&1) when spawning the RPC server.","Audit env vars and wrappers that enable verbose stdout logging in the server."],"tags":["rpc","protocol","json","stdout","parsing"],"backgroundTag":"invalid-json-response","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}