{"record":{"id":"d8f494435d25240f","repo":"CoplayDev/unity-mcp","slug":"path-expected-to-remove-exactly-1-line-line","errorCode":null,"errorMessage":"{path}: expected to remove exactly 1 line '{line}', removed {removed}","messagePattern":"(.+?): expected to remove exactly 1 line '(.+?)', removed (.+?)","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"tools/prepare_unity_asset_store_release.py","lineNumber":58,"sourceCode":"            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:\n        raise RuntimeError(\n            f\"{path}: expected to remove exactly 1 line '{line}', removed {removed}\")\n\n    write_text(path, \"\".join(kept))\n\n\ndef backup_dir(src: Path, backup_root: Path) -> Path:\n    ts = dt.datetime.now().strftime(\"%Y%m%d-%H%M%S\")\n    backup_path = backup_root / f\"{src.name}.backup.{ts}\"\n    shutil.copytree(src, backup_path)\n    return backup_path\n\n\ndef main() -> int:\n    parser = argparse.ArgumentParser(\n        description=\"Prepare MCPForUnity for Asset Store upload.\")\n    parser.add_argument(\n        \"--repo-root\",\n        default=str(REPO_ROOT_DEFAULT),","sourceCodeStart":40,"sourceCodeEnd":76,"githubUrl":"https://github.com/CoplayDev/unity-mcp/blob/c21bf496bca87d54e75bad048563c3adb1782081/tools/prepare_unity_asset_store_release.py#L40-L76","documentation":"Raised by remove_line_exact() when the target line content is not found exactly once in the file. The function strips each line and compares it to the literal passed in; it aborts if the count is 0 (line absent or reformatted) or >1 (duplicate lines). This is a build-time guard ensuring the Asset Store release script's source-edit assumptions still hold against the current source tree.","triggerScenarios":"Called as remove_line_exact(setup_service, \"[InitializeOnLoad]\"). Fires when SetupWindowService.cs no longer contains a standalone line whose stripped value equals exactly \"[InitializeOnLoad]\". Occurs if the attribute was already removed in a prior run, moved onto the same line as a class/method declaration (so strip() yields a longer string), wrapped in a region, or if the attribute now appears on multiple lines.","commonSituations":"The source file evolved after the release script was written — e.g. the [InitializeOnLoad] attribute was inlined, commented out, or merged with a comment. Also fires on a second accidental run if the first already removed the line (removed=0).","solutions":["Open SetupWindowService.cs and grep for 'InitializeOnLoad' to see the current form of the attribute line.","If the attribute sits on the same line as other tokens, update the literal passed to remove_line_exact (or switch to a regex-based removal) so the stripped comparison matches.","If the attribute was already removed (removed=0), the edit is a no-op — skip the call or make it idempotent by treating removed==0 as success.","If removed>1, disambiguate which occurrence to remove by adding surrounding context to the match logic."],"exampleFix":"// before\nremove_line_exact(setup_service, \"[InitializeOnLoad]\")\n\n// after — idempotent: tolerate already-removed attribute\ndef remove_line_optional(path: Path, line: str) -> None:\n    original = read_text(path)\n    lines = original.splitlines(keepends=True)\n    kept = [l for l in lines if l.strip() != line]\n    if len(kept) != len(lines):\n        write_text(path, \"\".join(kept))","handlingStrategy":"validation","validationCode":"from pathlib import Path\n\ndef can_remove_line(path: Path, line: str) -> bool:\n    if not path.is_file():\n        return False\n    matches = [l for l in path.read_text(encoding=\"utf-8\").splitlines() if l.strip() == line]\n    return len(matches) == 1\n\n# Before calling remove_line_exact:\nif not can_remove_line(setup_service, \"[InitializeOnLoad]\"):\n    print(f\"Skip: line not found exactly once in {setup_service}\")","typeGuard":null,"tryCatchPattern":"try:\n    remove_line_exact(setup_service, \"[InitializeOnLoad]\")\nexcept RuntimeError as e:\n    print(f\"Warning: skipping attribute removal — {e}\")\n    # decide whether to abort or continue; the edit may already be applied","preventionTips":["Run the release script with --dry-run first to validate path assumptions before source edits.","Keep the line literals in the script in sync with the source files via a pre-flight grep check.","Treat removed==0 as idempotent success if the script may be re-run."],"tags":["build-script","release-tooling","source-edit","validation"],"backgroundTag":null,"analyzedSha":"c21bf496bca87d54e75bad048563c3adb1782081","analyzedAt":"2026-08-13T17:36:56.095Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}