Porting python-semver to Go on a phone, an old PC, and 41 hours

I'm Krishna, 18, in Class 12 prepping for JEE. When PORT MORTEM 2026 showed up, I didn't have a laptop for it. What I had was a Samsung A05 with Termux, and an old Core 2 Duo PC running Linux Mint Xfce that I could only get to the next morning.. But I really wanted to participate in a Hackathon, So my plan became: start on the phone doing whatever needs zero compute, move to the PC once I could sit at it, and don't pick a repo that punishes me for being resource constrained.
I picked python-semver. It parses and compares version strings like 1.2.3-alpha.1. No I/O, no state, just numbers and string rules.. It was also easy to understand, i.e. easy to get what this even does. The hackathons own examples pointed at node-semver as a good target for exactly this reason, python-semver is basically its Python sibling, and Python is the language I actually know well from every other project I've built.
Track: Open Pair, since Python to Go isn't one of the official pairings.
I did created the folder and all from my phone and used git from Termux to push the folder and asked Claude for what will I need to do, to prove the fuzzing and how to make a hashmap(my first time doing so!), after getting that, i added it to the push, also i generated that Personal Access Token (PAT) for 30 days, copied it and pinned it into my clipboard.
The next day, i did my first fuzzing test, then something broke..
What actually broke
I built a differential fuzz harness in Python that generates random and adversarial version strings, feeds them into both the real python-semver and my compiled Go binary, and diffs the results. First real run at 5000 cases came back with 5 mismatches. All of them involved a version where patch was 0 ;)
Turned out to be a bug in my own test harness, not the port. I'd set omitempty on the numeric JSON fields in the Go bridge, and omitempty silently drops any field equal to its zero value, including a legitimately zero patch number. So the bridge would send no "patch" field at all instead of "patch": 0, and my Python side read the missing key as None, which obviously didn't match 0. Removed omitempty from the numeric fields, reran, 0 mismatches ;)
The next one was a real bug. bump_build("8.35.21+V00") gave "V1" in my Go port but "V01" in python-semver. The original preserves the zero padded width when incrementing a numeric identifier inside a string, mine just parsed the digits to an int and reformatted plain, losing the padding. Fixed it by padding the result back to the original digit width instead of doing a plain integer to string conversion. Reran at 5000 cases, then again across a handful of different seeds to make sure it wasn't a one off. Stayed at 0 (Phew).
The mistake I had almost shipped
Part of this hackathon's rule is you hash the original repo's test suite at the start so nobody can quietly edit the tests to make their port pass. I did that on day one, but I cloned the default branch instead of pinning to a released version. Meanwhile my fuzz harness was running against whatever pip install semver actually gives you, which is the last published release, not main.
For most of the functions I ported this didn't matter because the code was identical between main and the release. It only became visible once I started porting next_version,which was not even in the original plan btw, but I decided to do it..which had been rewritten on main and wasn't in the published release yet. My hand traced expected output didn't match what pip's semver actually returned, and diffing the two sources side by side showed exactly why!
Fixed it by checking out the actual release tag (3.0.4) in my local clone, recomputing the test suite hash against that pinned commit, and updating the README to reference the correct commit hash. Everything I'd already fuzzed before that point was still valid since none of that logic had changed between main and the release, but the citation in my README was wrong until I caught it.
The decision I'd take back
Hashing against main instead of a pinned release tag on day one. It cost me nothing in the end because I caught it before submitting, but it easily could have been the kind of inconsistency a judge notices and starts poking at. Lesson learned: always pin to an actual release, never a moving branch, when you're making a provenance claim.
What I cut on purpose
I didn't port match(), the range syntax thing like >=1.0.0 <2.0.0. It's basically its own small parser, meaningfully more scope than everything else combined, and I'd rather ship something fully proven than something bigger with an unfuzzed corner. I did end up adding next_version() later since it turned out to just be composition of functions I'd already ported and proven, so the risk to add it was low.
Where it landed
Parse, compare, all three bump functions, bump_prerelease, bump_build, finalize_version, next_version, is_valid, all ported and passing differential fuzzing against the real python-semver 3.0.4 across roughly 30,000 generated cases and multiple random seeds, 0 mismatches on the final build.
And guess what?, it's 3.1x faster in parsing and a 363x faster in Comparing! (Ik it might be due to python being python and golang being golang, but ain't it amazing?)
I didn't had a mic for my PC, I had two options (for making the video), use DroidCam to record while speaking, but my PC setup is near street, so it would be noisy.. or (the one I did) Record audio somewhere quiet (it didn't came out that much quiet though), play it and start recording with it.
Repo's here: https://github.com/kosmoscpp/port-semver-go
Video:
https://youtu.be/SYA3pwTXHAw?si=gstEMU4o_qNAHDY9
Tagging Hackathon Raptors 🦖

Top comments (0)