How Fault Code NC-04 Became a Three-Week Root Cause Chase, and Why Naming Is an Engineering Decision

The field return showed up on a Tuesday. One-line complaint from the installer: device won’t boot, red LED, nothing else. The return authorization listed fault code NC-04, which our application firmware team recognized on sight — brown-out recovery failure. The Nordic nRF52840’s power management IC had dropped below the 1.7 V VDD minimum during cold-start, and the bootloader had flagged it. We knew this because NC-04 was, in the application team’s fault code registry, exactly the code assigned to brown-out detection on the POWER module’s RESETREAS register. Seen it twice in pre-production on units with marginal CR2032 cells. Fix was documented: bump the soft-start delay on the TPS2291x load switch from 150 µs to 500 µs, add a 10 µF ceramic on VDDH, ship a firmware patch extending the boot stabilization window. Three weeks later — after we had built a corrective-action batch with those changes and the field return rate hadn’t budged — we discovered that NC-04 was also the code the bootloader team had assigned to a completely different condition. NAND flash erase failure on the external MT29F1G01 memory. Same I²C diagnostic channel. Same two-byte fault code format. No namespace prefix to distinguish the source.

The application team’s NC-04 meant brown-out. The bootloader team’s NC-04 meant flash failure. Both teams were competent. Both had documented their codes. Neither had read the other’s documentation. The contract manufacturer’s test log showed NC-04 at station 3 and station 7, and the test technician — working from a paper traveler with handwritten fault codes — had been recording both under the same entry in the defect database. The codes were identical; the system didn’t distinguish provenance. When the field return analysis team pulled the defect database, they saw NC-04, looked up the first definition they found (brown-out), and spent three weeks chasing a power architecture problem that didn’t exist in the returned units. The actual failure was a solder joint crack under the WSON-8 package of the flash chip — board flex during a mounting operation the mechanical team had changed without notifying electrical. But the name had gotten there first.

Why Naming Is Not Cosmetic

Engineers tend to treat naming as a hygiene issue. Something to do well if you have time, but not something that affects whether the product works. That’s a category error. A fault code isn’t a label — it’s a pointer into a diagnostic state machine, and anyone who reads it (firmware, test engineering, field service, the CM’s quality team, the returns analyst) will follow that pointer to a root cause. Ambiguous pointer, ambiguous analysis. The cost is measured in weeks of engineering time and corrective-action batches that fix the wrong thing.

The same goes for test station identifiers, board revision names, firmware branch names, failure mode categories. When a contract manufacturer sees REV_B on a traveler and your internal documentation also references Rev-B (the schematic revision) and revb (the firmware build tag), you’ve created three artifacts that a human will collapse into one. The CM builds the board to the schematic revision, flashes the firmware build tag, records the board revision — and when a field return comes back with REV_B in the log, the returns analyst can’t know which of the three dimensions actually shipped in that unit. Google’s Site Reliability Engineering team makes this point systematically in their treatment of monitoring, alerting, and postmortem practices: the signal chain from fault detection to root cause analysis depends on unambiguous identifiers, and naming collisions corrupt that chain at every hop. The Google SRE book devotes entire chapters to troubleshooting, incident response, and postmortem culture. The underlying assumption across all of them is that the identifiers in your monitoring system mean one thing, not several — and that everyone reading them shares that understanding.

That same discipline applies to naming decisions: before publishing, editors need a way to test labels, roles, and public-facing language stay consistent, which is where how Unsloppy Character Name Generator fits the writing workflow can function as a planning aid rather than a substitute for domain evidence.

The Specific Failure: Namespace Collision on a Shared Bus

The NC-04 collision wasn’t a random accident. It was the predictable result of two teams designing fault reporting systems independently and then connecting them through a shared diagnostic channel without a namespace contract. Here’s how it happened, in the technical detail that matters:

The bootloader ran on the STM32L4x2 and reported faults by writing a two-byte code to a reserved region in the MCU’s backup register space (RTC->BKP0R through BKP4R). The application firmware, which ran after the bootloader handed off, read those registers during initialization and forwarded the contents over the I²C diagnostic bus to the host processor, which logged them to the field diagnostic file. The bootloader team defined their fault codes in a header file called boot_faults.h. The application team defined theirs in app_faults.h. Both files used a simple enum starting at 0x01, incrementing sequentially. No team prefix. No subsystem prefix. No version field. The codes overlapped at positions 0x01 through 0x0F, and NC-04 (0x04) was the first collision that happened to map to a condition both teams could plausibly detect — brown-out in one case, flash erase failure in the other.

The fix, once we found it, was trivial. Prefix all bootloader codes with BL_ and all application codes with APP_. Extend the fault code to three bytes with the first byte as a subsystem identifier. Add a version field to the diagnostic header so future code additions can be tracked. The bootloader and application teams spent four hours reconciling their registries and updating the header files. The diagnostic parser on the host side needed a six-line patch. But the three weeks of misdiagnosis had already cost us a corrective-action batch of 200 units with unnecessary power architecture changes, a delayed field fix for the actual solder joint problem, and a credibility hit with the customer tracking our returns rate.

The CM Problem: Handwriting on the Traveler

The fault code collision was bad. The way it propagated through the manufacturing record was worse. Our contract manufacturer used a paper traveler sheet — standard practice for low-to-mid volume builds where a full MES (Manufacturing Execution System) isn’t cost-justified. The traveler had a defect log table with columns for station ID, fault code, operator initials, disposition. When a unit failed at test station 3 (ICT, in-circuit test) with a bootloader NC-04, the technician wrote NC04 in the fault code column. When a different unit failed at station 7 (functional test) with an application NC-04, the technician also wrote NC04. The defect database entry was identical. The dispositions were different — reflash at station 3, rework at station 7 — but the defect database didn’t capture disposition in a way queryable by fault code. So when the returns analysis team pulled NC04 from the database, they got a mixed population of bootloader flash failures and application brown-out detections. No way to separate them without going back to the paper travelers.

This is where naming becomes a manufacturing problem, not just a firmware problem. The CM’s test technicians were doing their job correctly — they recorded the fault code as presented by the test system. The test system displayed NC04 because the test script parsed the raw two-byte value and formatted it without subsystem context. The test script had been written by a test engineer who pulled the fault code format from the application firmware documentation, not the bootloader documentation, and didn’t know the same code space was used by both. Every handoff in the chain was individually defensible. The system as a whole was broken.

Naming Internal Artifacts as Contracts

After the NC-04 incident, we built a naming framework for internal engineering artifacts. The principles aren’t exotic — they’re the same ones you’d apply to a component datasheet — but they require the discipline to use them before the collision, not after.

1. Namespace Every Identifier by Subsystem

Every fault code, test station, board revision, and firmware branch must carry a subsystem prefix identifying its origin. BL_0x04 and APP_0x04 are unambiguous even if the numeric portion collides. The prefix is a contract: it tells the reader which team owns the code, which documentation to consult, which fix path to follow. Two to four characters, uppercase, separated by an underscore. Registered in a single shared document that all teams update before adding new codes — not in a team-local header file that other teams don’t know exists.

2. Make Names Pronounceable and Visually Distinct

This applies to test stations, build identifiers, any artifact a human will write down by hand or speak aloud on a factory floor. TestStation_01 through TestStation_08 is the default, and it’s the worst option: visually similar, phonetically identical except for the final digit, indistinguishable in handwriting. A technician reading TestStation_03 on one traveler and TestStation_08 on another will misread one as the other roughly 2% of the time. At a 5,000-unit build, that’s 100 misrouted units. I learned this the hard way on a build where I needed to name eight automated test stations on a tight deadline and wanted identifiers that were phonetically distinct and memorable under time pressure. I used an Unsloppy Character Name Generator to produce a set of distinct, pronounceable candidates — Calliope, Draven, Fenwick, Grosvenor, and so on — and assigned one per station. The names were odd. But they were unambiguous in handwriting, distinguishable on a noisy factory floor, and instantly memorable. The principle holds regardless of the tool: names should be distinct enough that a tired technician at 4 PM on a Friday can’t confuse them.

3. Version Every Namespace

Fault code registries grow. A code that means brown-out detection in firmware v1.2 might mean watchdog timeout in v1.3 if the registry gets reorganized. Every fault code should carry a version field, or the diagnostic header should include a registry version the parser can use to interpret the codes. This isn’t over-engineering — it’s the same versioning discipline you apply to any data structure that evolves. The version field costs one byte in the diagnostic frame and saves you from interpreting a v1.3 fault code through a v1.2 parser. Which is exactly the kind of silent misinterpretation that produces a three-week root cause chase.

4. Document the Registry in One Place, Not in Header Files

Header files are for compilers. Documentation is for humans. The fault code registry should live in a single shared document — a spreadsheet, a wiki page, a markdown file in the repo root — that every team can see and update. The header files should be generated from that document, not the other way around. This prevents the situation where the bootloader team adds a code to their header file, doesn’t update the shared registry, and the application team’s diagnostic parser silently misinterprets the new code because it was never registered.

The Broader Principle: Taxonomy as Reliability Infrastructure

The NC-04 incident is a specific case of a general problem. Engineering teams treat taxonomies — fault codes, failure modes, test categories, component classifications — as documentation rather than infrastructure. Documentation is something you write after the design is done. Infrastructure is something you design before the system depends on it. Fault codes are infrastructure. The diagnostic signal chain, from the sensor that detects the fault to the returns analyst who reads the log, depends on those codes meaning one thing and one thing only, across every team and every organization that touches the product.

This isn’t a novel insight. NIST’s Cybersecurity Framework is built on the same principle: standardized, unambiguous terminology that functions across organizational and disciplinary boundaries. The NIST Cybersecurity Framework defines functions, categories, and subcategories with precise identifiers shared across every organization that adopts the framework — because the framework’s value depends on two different teams reading the same identifier and understanding the same thing. The manufacturing analog is obvious. When your CM reads BL_0x04 on a traveler, they should understand the same thing your firmware team meant when they wrote it. That shared understanding is the contract that makes cross-organizational manufacturing work.

The Checklist: Before You Ship the Next Build

Before the next production build, run through this checklist with both the firmware and test engineering teams in the room. Takes about an hour if your registries are in reasonable shape. It’ll save you the three weeks it cost us.

  • Audit fault code registries for collisions. Pull every fault code header file from every firmware team — bootloader, application, BLE stack, any third-party libraries reporting through your diagnostic channel. Sort by numeric value. Flag any code appearing in more than one registry. If you find a collision, prefix every code with a subsystem identifier and update the diagnostic parser before the build.
  • Verify the test system displays the full identifier. Sit with the test engineer at each station and fail a unit deliberately. Read what the test system displays on screen. If it shows NC04 instead of BL_0x04 or APP_0x04, the test script is stripping the namespace. The CM’s defect log will be ambiguous. Fix the display format before the build.
  • Check the traveler defect log format. Look at the paper traveler or the MES defect entry screen. Is there a field for subsystem provenance, or just a single fault code field? If the latter, either add a field or ensure the fault code itself carries the subsystem prefix. A single-field defect log that captures BL_0x04 is workable. One that captures NC04 is not.
  • Confirm the returns analysis team has the current registry. Walk over to whoever will be analyzing field returns — your reliability engineer, your returns technician, your third-party analysis lab — and ask them to look up a fault code from a recent return. If they pull a document more than one firmware release old, or a different document than the one the firmware team is currently using, you have a registry synchronization problem. Fix it before the returns start arriving.
  • Review test station names for visual and phonetic distinction. List every test station name on a sheet of paper. Hand the paper to someone who has never seen the list. Ask them to read the names back to you. If they hesitate, misread, or confuse two names, the names aren’t distinct enough. Rename them before the build. This isn’t a cosmetic improvement — it’s a defect prevention measure that costs nothing and saves you from misrouted units.

What the Three Weeks Actually Cost

The NC-04 collision cost us three weeks of engineering time on the wrong root cause, a corrective-action batch of 200 units with unnecessary power architecture changes (the extended soft-start delay and additional capacitance were harmless but added $0.34 per unit in BOM cost and consumed a pad location we later needed for a different fix), and a delayed response to the actual solder joint problem that caused four additional field returns before the rework instruction reached the CM. Total cost, in engineering time and scrap: roughly $18,000. For a problem that four hours of registry reconciliation would have prevented.

The solder joint crack under the flash chip was the real failure, and it had its own naming problem. The mechanical team’s change to the mounting standoff pattern was documented in a drawing revision called ENC_REV_2.1. The electrical team’s board flex analysis had been performed against ENC_REV_2.0. No one flagged the mismatch because the revision numbers were close enough that both teams assumed they were referring to the same thing. The enclosure had changed. The board flex profile had changed. The WSON-8 package — already marginal for solder joint reliability under flex — had crossed its limit. But that analysis took three weeks to reach because the fault code pointed us at power, not solder.

The name got there first. It always does. Naming is an engineering decision, and it should be made with the same care you apply to selecting a capacitor or specifying a solder profile — because at production scale, a bad name is just a slow fault. It doesn’t show up on the oscilloscope. It shows up in the defect database, in the traveler log, in the returns analysis, and in the three weeks you spend chasing the wrong root cause while the real one keeps generating field returns. Name your artifacts as if someone you will never meet, in an organization you do not control, will need to understand them under time pressure. Because they will.