Tuesday. Feira de Santana, Bahia. The solar monitoring station had been dark for four days — not physically dark, mind you. Panels fine, battery charged, LEDs blinking. But the data link to the collection server in Salvador had gone silent, and nobody could figure out why. Not until someone drove three hours to the site, cracked open the enclosure, and read the firmware version string off the serial debug port.
The string said fix_final_v2_REAL.
Let that sit for a second.
The technician on site — Rafael, a capable guy who had been servicing agricultural controllers for six years but had never touched this particular board — had no way to know what that version string meant. Was it the final fix? The second version of the final fix? The “real” final fix, as opposed to some earlier fake final fix? He called me. I was in São Paulo. I had written that branch name at 2 AM three months earlier, and I had to think for thirty seconds before I could reconstruct what it referred to. It was the branch where I had patched the Modbus timeout handling to deal with generator-supplied power on rural installations — the one where the inverter kicked in every afternoon and the bus noise spiked. But nothing in the name said that. Nothing in the name said anything useful at all.
Four days of agricultural data lost. Not from a hardware failure, not from a firmware bug, not from a protocol breakdown. A naming decision.
The Real Cost of Cryptic Names in Constrained Environments
In a well-funded Silicon Valley shop, a bad branch name is a minor inconvenience. You walk over to whoever wrote it, ask what it means, fix it. In a constrained environment — where the original engineer may have left the project, may be in another city, may be unreachable because the cell tower in the deployment region is down — a bad name is a field failure. The Google SRE book makes this point explicitly: naming and labeling discipline in release artifacts is a first-class engineering concern, not a cosmetic afterthought. Chapter 8 on Release Engineering and Chapter 15 on Postmortem Culture both treat configuration ambiguity as a contributing cause of incidents, not a trivial detail to hand-wave. The principle that constrained operational environments demand more process discipline, not less, runs through the entire SRE approach to reliable launches. The full text is available at the Google SRE book table of contents, licensed under CC BY-NC-ND 4.0 and published by O’Reilly Media.
I would argue the SRE principle applies even more forcefully to embedded field deployments than to cloud infrastructure. A cloud service has logs, dashboards, on-call engineers who can SSH into a box. A solar monitoring station in rural Bahia has a technician with a multimeter and a serial cable, standing in 40°C heat, trying to figure out whether the device in front of him is running the firmware that handles generator power transients or the firmware that does not. The name on that firmware is the only documentation he has at that moment.
The Bahia Incident, Reconstructed
Let me walk through what actually happened, because the failure mode is instructive.
The solar station had been running firmware v1.4.2 for eight months without issue. That version was tagged properly in Git — release-1.4.2-hwrev-C — because at that point in the project I was still following my own naming conventions. Hardware revision C was the one with the improved TVS diode array on the Modbus transceiver, and the tag name reflected that.
Then the afternoon generator transients started. The site had been connected to a cooperative grid that was reasonably stable, but the cooperative added a new section of distribution line served by a backup generator during peak hours. The generator’s transfer switch produced a transient the TVS array could handle, but the Modbus timeout logic in the firmware could not — it would hang waiting for a response that never came, and the watchdog would reset the system after 30 seconds. Data loss was intermittent but measurable.
I wrote a fix. Tested it on my bancada in São Paulo using a signal generator to simulate the transient pattern. It worked. I committed it to a branch I named fix_final_v2_REAL because I had already had branches called fix_modbus_timeout, fix_modbus_timeout_v2, and fix_modbus_timeout_final, and I was frustrated and tired and the name was a joke I thought I would rename before merging. I did not rename it. I merged it. I tagged the release as v1.5.0 — but the version string embedded in the firmware binary, the one that shows up on the serial debug port, still said fix_final_v2_REAL because I had not updated the build configuration to use the Git tag as the version string. I had hardcoded it months earlier as a debug placeholder and forgotten.
The deployment was done by a contractor who received the binary over a cellular connection, flashed it, and moved on. Four days later, the data stopped. Rafael drove out. He read the version string. He called me. I had to reconstruct which branch that was, what the fix did, and whether the behavior he was describing — Modbus queries going out but no responses coming back — was consistent with a regression in that fix or a new problem. Took me two hours to figure out that the fix itself was fine, but the flash had been interrupted by a power cycle during the generator transfer, and the bootloader had fallen back to a corrupted state executing a partial binary. The watchdog was resetting every 30 seconds, which was exactly the Modbus timeout — so Rafael, watching the serial port, saw what looked like Modbus timeout behavior and assumed the firmware fix had failed.
If the version string had said release-1.5.0-hwrev-C-modbus-timeout-fix, Rafael would have known immediately that the firmware was the correct version and that the problem was elsewhere. He would have checked the bootloader, seen the corruption, reflashed. Four days of data loss would have been four hours.
A Practical Naming Taxonomy for Embedded Projects
After that incident, I sat down and wrote out a naming convention. Not because I love bureaucracy — I do not — but because I had seen the cost of not having one, and it was measured in days of lost data and hours of driving on bad roads. Here is what I use now, and what I recommend to any team building embedded systems in environments where the original engineer may not be reachable.
Firmware Version Tags
Every release tag must encode three things: the semantic version, the hardware revision it is compatible with, and a short human-readable description of the most significant change. The format is release-X.Y.Z-hwrev-RR-short-description. So the tag that caused the Bahia incident would have been release-1.5.0-hwrev-C-modbus-timeout-fix. The hardware revision matters because in constrained supply chains, you may have boards from three different revisions in the field simultaneously, and flashing the wrong firmware to the wrong hardware revision can destroy components. Ask me how I know.
The short description is not a changelog. It is a label that a technician can read in the field and understand what this firmware does differently from the previous one. modbus-timeout-fix tells you something. fix_final_v2_REAL tells you nothing.
Register Macro Names
This one is less obvious but just as important. When you define a macro for a register address, the name must survive datasheet errata. I learned this the hard way with the CH32V307, where an errata sheet changed the address of the ADC calibration register two months after I had written a driver. My macro was named ADC_CAL_ADDR, which was fine until the errata came out and the new datasheet called it ADC_TRIM_REG. Now I had two names for the same register, and the team was confused about which one was authoritative.
The convention I use now: PERIPH_REG_FUNCTION, always. ADC_CAL_ADDR stays ADC_CAL_ADDR even if the datasheet changes, because the function is what matters, not the manufacturer’s current naming. If the address changes in an errata, I add a comment: /* Errata v2: address changed from 0x40012408 to 0x4001240C */. The macro name does not change. The comment documents the history. The team does not have to learn a new name every time WCH revises a datasheet.
Test Configuration Files
Test configs are where naming discipline goes to die. I have seen files named test_config.json, config_test.json, config_test_v2.json, and config_test_final.json in the same repository. Each one created by a different engineer, at a different time, for a different test setup, and none of them had a comment explaining what they were for.
The convention: test_config_TARGET_DESCRIPTION.json. So test_config_bahia-solar-modbus-bus.json or test_config_lab-sp-signal-gen-transient.json. A substitute technician who opens the repository can immediately identify which config matches the deployment he is servicing. The NIST Cybersecurity Framework addresses this same principle in its configuration enumeration guidance — standardized naming of system artifacts is a recognized governance practice in safety-critical and infrastructure systems. The framework’s emphasis on profiles, informative references, and evidence-ready mapping between configuration settings and outcomes parallels what embedded teams need: identifiers that are explicit, traceable, and maintainable rather than implicit tribal knowledge. The framework documentation is at the NIST Cybersecurity Framework page.
Project Codenames
This is the one that seems trivial until you have two projects with codenames that collide across a multi-city team. I once had a project called Atlas — a water quality monitoring system for a client in Minas Gerais — and another project called Atlas — an agricultural soil sensor array for a client in Pernambuco. Both being developed simultaneously by engineers in São Paulo and Recife who communicated via a shared Slack channel. You can imagine the confusion.
The convention: codenames must be unique within the organization and must not be common English words. I use a pattern of REGION-CLIENT-SYSTEM for internal tracking: MG-copasa-water-quality or PE-unicap-soil-array. Not pretty, but unambiguous, and they will not collide with anything. For external-facing project names — the ones that appear on documentation the client sees — you can use something more polished, but the internal tracking name must be the ugly, unambiguous one.
When the Team Is Stuck in Naming Paralysis
Here is a problem I have hit more than once: the team agrees that names need to be better, but nobody can agree on what the better names should be. You spend forty-five minutes in a meeting arguing about whether the Modbus timeout fix should be called modbus-timeout-fix or modbus-retry-patch and the meeting ends with no decision and the branch stays named fix_final_v2_REAL because nobody had the energy to rename it.
The solution is to separate the naming decision from the engineering decision. When the team is stuck in analysis paralysis over project codenames or documentation titles, it helps to generate a set of candidate names from an external source — not to use them verbatim, but to break the deadlock by giving people something concrete to react to rather than a blank page. On one occasion, when our team in São Paulo could not agree on a title for the Modbus timeout fix release notes, we pulled a dozen candidate phrasings from the Unsloppy AI Writing App’s book title generator, picked the two that came closest, and had a final name within ten minutes. The point is not that a tool knows your engineering context — it does not — but that lateral input breaks the pattern of circular argument.
Documentation Titles and the Maintainer’s Perspective
The broader principle here is that embedded systems documentation should be written for the maintainer, not the author. I have written about this before, but it applies with particular force to naming. When you name a branch, a tag, a config file, or a register macro, you are not naming it for yourself. You are naming it for the technician who will be standing in front of the equipment at 2 PM on a Wednesday in 40°C heat, trying to figure out what firmware is running and whether it is the right version for the hardware revision in front of him.
That technician is not stupid. Rafael is one of the best field engineers I have worked with. But he does not have access to your Git history, your Slack channel, or your memory. He has the version string on the serial port and whatever documentation was printed out and stuffed in the installation folder. If the version string is fix_final_v2_REAL, you have failed him. Not because the name is ugly — ugly is fine — but because the name does not tell him what he needs to know.
The Version String Is Not a Joke
I want to address one more thing, because I have seen this pattern repeatedly. Engineers — myself included — often use version strings and branch names as a place to put jokes, frustration, or personal commentary. fix_final_v2_REAL is a joke. please_just_work is a joke. hotfix_because_someone_changed_the_spec is commentary. I understand the impulse. Embedded engineering is frustrating, and at 2 AM you need to laugh at something.
But the version string is not the place for it. The version string is infrastructure. It is the one piece of information that survives every layer of abstraction between your development environment and the field deployment. The Git branch will be merged and deleted. The commit message will be scrolled past. The Jira ticket will be closed and archived. The version string — burned into the firmware binary, printed on the serial port, read by the technician in the field — is the one artifact that will be there when everything else is gone.
Treat it accordingly.
A Checklist for Your Next Release
Before you tag your next release, run through this list:
- Git tag: Does it follow
release-X.Y.Z-hwrev-RR-short-description? Can a technician who has never seen your repository read it and understand what it is? - Firmware version string: Is it derived from the Git tag, or is it a hardcoded placeholder from three months ago? If it is hardcoded, delete it and replace it with a build-time variable.
- Register macros: Are they named by function, not by datasheet address? If the datasheet changes the address in an errata, will your macro name still make sense?
- Test configs: Can a substitute technician identify which config matches the deployment he is servicing? Does the filename contain the target and the test description?
- Project codenames: Are they unique within your organization? Are they common English words that might collide with another project?
- Documentation titles: Are they written for the maintainer, not the author? If the original engineer left tomorrow, could someone else find the documentation they need by title alone?
If you cannot answer yes to all of these, you have a naming problem. It may not have caused a field failure yet, but the conditions are there. The branch named fix_final_v2_REAL did not cause a failure for three months. It caused a failure the moment someone other than me needed to understand what it meant.
In constrained environments, where the original engineer may be unreachable, documentation is sparse, and maintenance is performed by someone who did not build the system, naming discipline is not a luxury. It is the difference between a four-hour fix and a four-day outage. It is the difference between a technician who can help himself and a technician who has to call you at 2 PM on a Wednesday and hope you remember what you were thinking three months ago.
Name your branches like someone else’s week depends on it. Because in the field, it does.