{"record":{"id":"271281be9a02af58","repo":"stamparm/maltrail","slug":"s-expected-exactly-1-version-line-matched-d-refusing-to","errorCode":null,"errorMessage":"[!] %s: expected exactly 1 version line, matched %d\n    Refusing to write ANY file - a half-applied bump is the failure this script exists to prevent.","messagePattern":"\\[!\\] (.+?): expected exactly 1 version line, matched (.+?)\n    Refusing to write ANY file - a half-applied bump is the failure this script exists to prevent\\.","errorType":"console","errorClass":"SystemExit","httpStatus":null,"severity":"error","filePath":"sensor/tools/bump_version.py","lineNumber":94,"sourceCode":"def next_minor(version):\n    parts = version.split(\".\")\n    if len(parts) < 2 or not all(p.isdigit() for p in parts[:2]):\n        raise SystemExit(\"[!] cannot bump %r - expected 'major.minor'\" % version)\n    return \"%s.%d\" % (parts[0], int(parts[1]) + 1)\n\n\ndef apply(version, date_released=None):\n    \"\"\"Rewrite every file, or none of them.\"\"\"\n\n    planned = []\n    for path, pattern, repl in _edits(version):\n        text = _read(path)\n        new, n = re.subn(pattern, repl, text, flags=re.M)\n        # Exactly once. Zero means the file moved on and this script is now lying about\n        # covering it; more than once means the pattern is catching something it should not\n        # (a dependency's version key, say) and would corrupt the file.\n        if n != 1:\n            raise SystemExit(\"[!] %s: expected exactly 1 version line, matched %d\\n\"\n                             \"    Refusing to write ANY file - a half-applied bump is the \"\n                             \"failure this script exists to prevent.\" % (path, n))\n        planned.append((path, new, text != new))\n\n    if date_released:\n        path = os.path.join(ROOT, \"CITATION.cff\")\n        for i, (p, new, _) in enumerate(planned):\n            if p != path:\n                continue\n            out, n = re.subn(r'^(date-released\\s*:\\s*\")[^\"]*(\")',\n                             r'\\g<1>%s\\g<2>' % date_released, new, flags=re.M)\n            if n != 1:\n                raise SystemExit(\"[!] %s: expected exactly 1 date-released line, matched %d\" % (p, n))\n            planned[i] = (p, out, out != _read(p))\n\n    for path, new, changed in planned:\n        if changed:\n            with io.open(path, \"w\", encoding=\"utf8\") as f:","sourceCodeStart":76,"sourceCodeEnd":112,"githubUrl":"https://github.com/stamparm/maltrail/blob/77cfb06d7606506d101bbcec0786c77166c4255e/sensor/tools/bump_version.py#L76-L112","documentation":"apply() rewrites each known version-containing file by substituting its version line with re.subn and requires exactly one replacement per file. Zero matches means the file no longer matches the pattern (stale mapping), and >1 means the pattern hit multiple lines (e.g. a dependency's version key). In either case it aborts BEFORE writing any file, preserving the all-or-nothing guarantee.","triggerScenarios":"Running a bump when a tracked file's version line was renamed/removed (n=0), or when the version regex accidentally matches more than one line in a file (n>1), such as a pinned dependency version sharing the same line format.","commonSituations":"Upstream Maltrail files were updated and their version line changed shape; a new file or dependency block was added containing a second line matching the pattern; a previous partial edit left duplicate version lines.","solutions":["Inspect the named file and restore/fix exactly one version line matching the script's pattern for that file.","If a dependency's version line collides with the pattern, make the pattern in apply() more specific (e.g. anchor to the variable name) or change the offending line's format.","If the file legitimately no longer carries a version, update the script's FILES table to drop it rather than forcing the bump."],"exampleFix":"// before\nVERSION = \"0.53\"\nDEPENDENCY_VERSION = \"0.53\"  # second match -> n != 1\n// after\nVERSION = \"0.54\"\nDEPENDENCY_VERSION = \"1.2.0\"  # distinct format, no collision","handlingStrategy":"validation","validationCode":"import re\nfor path, pattern in bump_version.FILES:\n    n = len(re.findall(pattern, open(path).read(), re.M))\n    if n != 1:\n        print(f'{path}: version pattern matched {n} times - fix before bumping')\n","typeGuard":null,"tryCatchPattern":"import subprocess\np = subprocess.run(['python', 'sensor/tools/bump_version.py', '--next'])\nif p.returncode != 0:\n    # apply() aborts before writing anything; repo is unchanged, fix files and retry\n    verify_no_files_modified()\n","preventionTips":["Keep exactly one version line per tracked file in the exact expected format","When upgrading upstream Maltrail files, re-check the version-line format still matches the script","Avoid adding dependency version pins that mimic the tracked version-line pattern"],"tags":["versioning","atomic-write","regex-match"],"backgroundTag":"internal-invariant-violation","analyzedSha":"77cfb06d7606506d101bbcec0786c77166c4255e","analyzedAt":"2026-09-13T03:50:16.010Z","contentChangedAt":"2026-09-13T03:50:16.010Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}