Recurring Build Antipatterns and Recovery¶
Page Maps¶
graph LR
family["Reproducible Research"]
program["Deep Dive Make"]
section["Migration Governance Tool Boundaries"]
page["Recurring Build Antipatterns and Recovery"]
capstone["Capstone evidence"]
family --> program --> section --> page
page -.applies in.-> capstone
flowchart LR
smell["spot the repeated smell"] --> class["name the form of truth loss or ownership drift"]
class --> smallest["choose the smallest honest recovery move"]
smallest --> protect["keep proof alive during the repair"]
protect --> prevent["write the rule that stops the smell returning"]
This page collects the patterns that keep returning even after teams say they already know better.
The goal is not to mock bad Makefiles. The goal is to recognize familiar damage early enough to stop it from spreading.
Why antipatterns matter this late in the course¶
By Module 10, you already know many isolated facts:
- honest prerequisites matter
- parallel safety matters
- target meaning matters
- release boundaries matter
- observability matters
The problem in real repositories is usually not missing isolated facts. The problem is that the same bad combinations keep reappearing under new names.
That is why this page is about patterns, not trivia.
A smell is a hypothesis, not a diagnosis¶
A large recipe, recursive Make, a stamp, or a shell helper is not automatically an antipattern. The diagnosis requires a causal chain:
flowchart LR
smell["visible shape"] --> challenge["controlled challenge"]
challenge --> signature["repeatable failure signature"]
signature --> truth["lost truth or blurred owner"]
truth --> repair["bounded recovery"]
repair --> regression["acceptance and rejection checks"]
For example, “this recipe is long” is a style observation. “The recipe writes the report, archive, and remote release record; a retry rebuilds a different archive” is an ownership and idempotency diagnosis.
The sentence to keep¶
When you spot a suspicious build habit, ask:
What repeated form of truth loss or ownership drift am I looking at, and what is the smallest honest recovery path?
That question turns vague discomfort into action.
Antipattern 1: phony ordering instead of real edges¶
This is one of the oldest mistakes:
.PHONY: prepare compile
all: prepare compile
prepare:
@mkdir -p build
compile:
@cc -o build/app src/main.c
This looks harmless. The deeper problem is that compile does not actually say what it
needs.
Signals:
- a target works only because a phony step happened first
-jexposes missing directories or missing generated inputs- downstream targets rely on ritual order instead of real prerequisites
Smallest honest recovery:
- make directory creation an order-only prerequisite where appropriate
- make generated inputs first-class graph nodes
- remove phony sequencing once real edges exist
Phony targets are not evil. Using them to hide real data or publication dependencies is.
Prove the diagnosis by requesting compile directly in an empty isolated workspace and
under -j. If the directory or generated input is missing, the failure signature
confirms that all was carrying ritual order rather than the graph carrying the
dependency. After repair:
- direct
make compilemust succeed make -j8 allmust succeed- changing the directory timestamp alone must not rebuild the artifact
Antipattern 2: multi-writer outputs hidden behind convenience¶
This appears in many forms:
- two targets both rewrite
build/app - a packaging target rebuilds product outputs
- a helper script refreshes generated files during unrelated commands
Teams often describe this as "convenient" because it avoids repeated commands. What it really does is erase output ownership.
Signals:
- outputs change when running unrelated targets
- incremental behavior becomes hard to trust
- release incidents are hard to reproduce
Smallest honest recovery:
- assign one writer to each trusted output
- split generation from packaging or publishing
- make convenience targets compose truthful targets instead of rewriting artifacts
Single-writer discipline is not style. It is a survival rule.
Distinguish a multi-writer from legitimate composition. Two phony targets may both
depend on build/app without writing it. The defect exists when two recipes or external
systems can publish the trusted path. Challenge it by recording the artifact identity,
running each route separately, and tracing the actual writer.
Antipattern 3: opaque orchestration hiding the graph¶
Some inherited builds are not recursive in the strict GNU Make sense, but they still act like it:
- shell scripts call
makein several places - one target delegates to directory-local mini systems without visible edges
- wrapper scripts choose routes dynamically based on host state
Signals:
- nobody can describe the real graph from the top level
--tracehelps less than expected because the interesting work happens elsewhere- CI failures depend on where the wrapper script happened to branch
Smallest honest recovery:
- keep the top-level contract small and explicit
- expose subsystem boundaries deliberately
- model shared outputs and inputs at the layer where they can be reviewed
- stop hiding major orchestration decisions in shell branching
The point is not "never call another tool." The point is "do not bury the ownership model."
The recovery does not require flattening every subsystem into one file. Preserve recursive boundaries when they have explicit inputs, outputs, and forwarding semantics. Repair the case where a wrapper chooses undeclared work from host state or where parent and child systems both claim the same artifact.
Antipattern 4: stamps and manifests with no clear meaning¶
Stamps and manifests can be excellent tools. They can also become places teams dump uncertainty.
Warning signs:
- a stamp exists because "Make needed it somehow"
- the stamp is touched every run
- nobody can explain what semantic boundary it proves
- consumers depend on the stamp while the real output shape remains unclear
Example:
The review question is:
- what single fact does this stamp prove?
If the answer is "sort of many things," the stamp is under-specified.
Smallest honest recovery:
- define the boundary fact in one sentence
- make the stamp or manifest represent only that fact
- keep consumers depending on real published outputs where direct edges are clearer
A stamp that is always touched creates a second signature: unchanged requests rebuild downstream targets forever. Test both relevant change and unchanged convergence. If the stamp represents several outputs, document whether it is a transaction commit marker or only a completion hint; consumers should not infer more than the file proves.
Antipattern 5: release or install routes that do too much¶
This pattern survives because it often "works" during demos:
The issue is not that each sub-step exists. The issue is that the target meaning is too broad to review.
Signals:
- one command mutates many different boundaries
- failures are described as generic "release issues"
- reruns are risky because side effects already happened
Smallest honest recovery:
- separate validation, packaging, install, and deployment meanings
- keep publication boundaries inspectable
- make dangerous side effects opt-in and explicit
When a target means everything, it usually means nothing clearly.
The split must preserve dependency order without preserving accidental side effects:
flowchart LR
validate["release-check\nlocal validation"] --> package["dist\nlocal artifact"]
package --> verify["verify-dist\nmanifest and checksum"]
verify --> handoff["publish\nsubmit existing artifact"]
handoff --> receipt["receiver-owned receipt"]
Rerunning publish should not silently regenerate dist. If the receiver accepts
idempotency keys or artifact digests, retry the same handoff object; do not turn a remote
retry into a local rebuild.
Antipattern 6: performance fixes that erase truth¶
This one often appears late, after the team has already suffered:
- checks are skipped because they are slow
- caches are added without modeling the cache boundary
- rebuilds are reduced by ignoring semantic inputs
- trace or audit routes are removed to reduce noise
Signals:
- the build is faster but less explainable
- stale outputs become harder to detect
- incidents take longer because evidence disappeared
Smallest honest recovery:
- measure where the real cost lives first
- remove waste, not obligations
- add bounded observability routes instead of deleting evidence surfaces
- keep truth-preserving comparisons during optimization work
Speed is valuable. Truth is more valuable.
Require paired performance evidence:
- workload and baseline route
- measured cost and intended improvement
- correctness checks that remain enabled
- relevant and irrelevant mutation behavior
- bounded diagnostic route for incidents
A cache that speeds unchanged requests but serves stale outputs after a semantic change is not an optimization. It is a truth defect with good benchmark numbers.
Use failure signatures to choose the pattern¶
| Observed signature | Likely pattern | Disambiguating challenge |
|---|---|---|
direct target request fails but make all works |
ritual ordering | request target in empty isolated workspace |
| artifact identity changes after unrelated target | multi-writer output | trace writers under each route |
| top-level trace stops before decisive work | opaque orchestration | capture recursive invocations and selected subsystem contract |
| unchanged request rebuilds every time | meaningless or volatile stamp | inspect stamp content and timestamp across two requests |
| remote retry changes local checksum | overgrown release route | retry with a fixed prebuilt artifact |
| benchmark improves while mutation check fails | truth-erasing optimization | challenge a semantic input omitted from the cache key |
Several patterns can coexist. Choose the earliest causal defect. If ritual ordering hides a missing generated edge and an overgrown release route happens to trigger the generator, repair the edge before treating target naming as the primary cause.
Keep proof alive while you recover¶
Antipattern repair goes wrong when the team fixes the smell and deletes the evidence that would prove the repair worked.
Keep at least one reviewable proof route alive while you change the graph:
| If the smell touches... | Keep this proof route alive |
|---|---|
| generated outputs | a comparison route between old and new generated files |
| package or publish steps | a dry-run or staging route that exposes exactly what would ship |
| orchestration wrappers | a traced route that shows the chosen execution path |
| performance shortcuts | a slower but trustworthy audit path that still models full truth |
If you cannot name the proof route, you are not ready to change the build yet.
Use one recovery rubric¶
When you detect an antipattern, classify the first recovery move:
| Antipattern smell | First recovery move |
|---|---|
| ritual order dependence | expose a real edge or order-only prerequisite |
| multi-writer output | assign one owner and remove hidden rewrites |
| opaque orchestration | surface the actual boundary and contract |
| meaningless stamp | define the represented fact before keeping the file |
| overgrown release target | split target meanings and side effects |
| truth-erasing optimization | restore evidence and semantic inputs first |
This keeps the fix proportional to the finding.
Recover in layers¶
Use this order when several defects overlap:
- preserve a reproducer and current failure signature
- protect trusted final paths from partial publication
- restore missing edges and single-writer ownership
- narrow public target and handoff meanings
- recover observability and pressure checks
- encode the prevention rule in governance
This is a causal order, not a delivery label. A repository may need a different sequence when external safety dominates, but do not begin with abstraction while corrupted outputs or hidden writers remain possible.
Every recovery needs two proof directions:
| Proof direction | Question |
|---|---|
| acceptance | Does the previously mishandled relevant change or failure now produce the intended result? |
| rejection | Does an unrelated change avoid work, and does the former competing writer no longer publish? |
Only the first direction makes a repair look effective. Both directions show that the ownership boundary is narrower and truthful.
Run a diagnosis pass before you touch the file¶
Before editing, write four short answers:
- what output, contract, or published state is currently being blurred?
- who should own that boundary when the repair is complete?
- what is the smallest change that makes the boundary more truthful?
- which proof route will tell you whether the repair actually helped?
That short diagnosis stops you from reaching for a heroic rewrite when the real problem is one hidden edge or one mixed-responsibility target.
Familiarity does not make an antipattern legitimate¶
Some patterns survive simply because teams have seen them for years:
- one giant
releasetarget - a helper script that "just knows" what to rebuild
- top-level targets that rewrite shared files as a convenience
- cleanup routes that also reset caches or developer state
Longevity does not make those healthy. It usually makes them expensive.
Module 10 asks you to say that clearly.
Review drill¶
For any suspicious build habit, ask:
- which repeated smell is this?
- what kind of truth loss or ownership drift does it cause?
- what is the smallest honest recovery step?
- what proof route must stay alive during the repair?
Recovery checkpoint¶
You are ready to move on when you can:
- name the antipattern without describing the whole repository history
- explain the specific truth loss or ownership drift it causes
- propose one bounded repair step instead of a vague cleanup campaign
- preserve at least one proof route while the change is happening
- name the governance rule and rejection test that would stop it from returning
If those answers are fuzzy, you have recognized the smell but not yet designed the recovery.
Capstone connection¶
Use the capstone or an inherited Make system to practice:
- naming one repeated antipattern clearly
- listing the observable signals that confirm it
- choosing the smallest repair that restores truth or ownership clarity
- naming the rule that would prevent the same shape from reappearing
That turns Module 10 from pattern recognition into maintenance discipline.
Exit check¶
Leave this lesson only when you can do all of these:
- explain one recurring build antipattern in terms of truth loss or ownership drift
- identify the smallest honest recovery move for it
- describe one rule or proof surface that would help stop it from coming back
- distinguish a visual smell from a reproduced failure signature
- provide acceptance and rejection evidence for the recovery