{"record":{"id":"81ea2491a46ca19c","repo":"CoplayDev/unity-mcp","slug":"path-expected-1-replacement-for-pattern-got-n","errorCode":null,"errorMessage":"{path}: expected 1 replacement for pattern, got {n}","messagePattern":"(.+?): expected 1 replacement for pattern, got (.+?)","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"tools/prepare_unity_asset_store_release.py","lineNumber":39,"sourceCode":").parents[1]  # adjust if you place elsewhere\n\n\ndef read_text(path: Path) -> str:\n    return path.read_text(encoding=\"utf-8\")\n\n\ndef write_text(path: Path, text: str) -> None:\n    path.write_text(text, encoding=\"utf-8\")\n\n\ndef replace_once(path: Path, pattern: str, repl: str) -> None:\n    \"\"\"\n    Regex replace exactly once, else raise.\n    \"\"\"\n    original = read_text(path)\n    new, n = re.subn(pattern, repl, original, flags=re.MULTILINE)\n    if n != 1:\n        raise RuntimeError(\n            f\"{path}: expected 1 replacement for pattern, got {n}\")\n    if new != original:\n        write_text(path, new)\n\n\ndef remove_line_exact(path: Path, line: str) -> None:\n    original = read_text(path)\n    lines = original.splitlines(keepends=True)\n\n    removed = 0\n    kept: list[str] = []\n    for l in lines:\n        if l.strip() == line:\n            removed += 1\n            continue\n        kept.append(l)\n\n    if removed != 1:","sourceCodeStart":21,"sourceCodeEnd":57,"githubUrl":"https://github.com/CoplayDev/unity-mcp/blob/c21bf496bca87d54e75bad048563c3adb1782081/tools/prepare_unity_asset_store_release.py#L21-L57","documentation":"Raised by replace_once in tools/prepare_unity_asset_store_release.py when a regex substitution did not match exactly once. The function is deliberately strict (it edits release files like package version/asmdef references) so that a drifted pattern surfaces immediately rather than silently leaving a file unchanged or over-replacing.","triggerScenarios":"re.subn(pattern, repl, original, flags=re.MULTILINE) returns n != 1: 0 when the pattern no longer matches the file (content drifted), or >1 when the pattern is too broad and now matches multiple lines.","commonSituations":"The release-prep script was not updated after the target file's format changed; the version string or token appears more than once after a refactor; the regex was written against an older package version.","solutions":["Open the path named in the message and re-derive the regex against current content so it matches exactly one line.","If the token legitimately appears multiple times now, switch to a more specific pattern (anchored line) or update the script to replace all and re-verify.","Re-run the script from a clean checkout to rule out partial prior edits leaving extra matches."],"exampleFix":"# before\nreplace_once(pkg_json, r'\"version\"', '\"1.0.0\"')  # matches 0 or >1\n# after\nreplace_once(pkg_json, r'^    \"version\": \"[^\"]+\",', '    \"version\": \"1.0.0\",')  # anchored, exactly 1","handlingStrategy":"validation","validationCode":"import re\nn = len(re.findall(pattern, read_text(path), flags=re.MULTILINE))\nif n != 1:\n    raise SystemExit(f'{path}: pattern matches {n} times; re-derive the regex')","typeGuard":"def matches_exactly_once(text: str, pattern: str) -> bool:\n    return len(re.findall(pattern, text, flags=re.MULTILINE)) == 1","tryCatchPattern":"try:\n    replace_once(path, pattern, repl)\nexcept RuntimeError as e:\n    print(e); sys.exit(1)","preventionTips":["Re-derive release-prep regexes whenever target file formats change","Anchor patterns to a full line for uniqueness","Run release prep from a clean checkout to avoid double matches"],"tags":["tooling","release","regex","validation"],"backgroundTag":null,"analyzedSha":"c21bf496bca87d54e75bad048563c3adb1782081","analyzedAt":"2026-08-13T17:36:56.095Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}