
No Workarounds
- 1 installs
- 95 repo stars
- Updated June 28, 2026
- pedronauck/kodebase-go
Enforces root-cause fixes over workarounds by detecting and rejecting hacks like type assertions, lint suppressions, and error swallowing.
About
Activates gate functions that catch common workaround patterns and push toward addressing the root cause instead of patching symptoms. A developer uses it while debugging, fixing bugs, or reviewing changes.
- Detection gate rejects type assertions, lint suppressions, error swallowing
- Enforces root-cause fixes over symptom patches
No Workarounds by the numbers
- 1 all-time installs (skills.sh)
- Ranked #982 of 1,352 Code Review & Quality skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/pedronauck/kodebase-go --skill no-workaroundsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1 |
|---|---|
| repo stars | ★ 95 |
| Last updated | June 28, 2026 |
| Repository | pedronauck/kodebase-go ↗ |
What it does
Enforces root-cause fixes over workarounds by detecting and rejecting hacks like type assertions, lint suppressions, and error swallowing.
Files
No Workarounds
The Fundamental Law
A WORKAROUND IS A LIE TOLD TO THE COMPILER.
It makes the symptom disappear while the disease spreads.A workaround is any change that makes a problem stop manifesting without addressing why the problem exists. Workarounds are not fixes. They are deferred failures with compound interest.
Philosophical foundation: Read references/philosophical-foundations.md for the engineering principles behind this skill, drawn from Toyota's Jidoka, Fowler's Technical Debt Quadrant, Torvalds' "good taste," and the Broken Windows Theory.
The Workaround Detection Gate
BEFORE writing or proposing ANY fix:
1. STATE the problem clearly
2. ASK: "Why does this problem exist?" (not "How do I make it stop?")
3. TRACE to root cause (use systematic-debugging skill)
4. ASK: "Does my proposed fix address the ROOT CAUSE?"
5. ASK: "Would this fix be necessary if the code were correct?"
6. ASK: "Am I silencing a signal or fixing a source?"
IF any answer reveals symptom-patching:
STOP — Redesign the fix to address root cause
IF root cause is in external code or truly unfixable:
Document why, add defensive validation, and mark with WORKAROUND comment
(See "The Escape Valve" section below)The Seven Categories of Workarounds
Category 1 — TYPE: Type System Evasion
The signal being silenced: The type system is telling the code is wrong.
// WORKAROUND: Lying to the compiler
const value = response.data as UserProfile;
const config = {} as AppConfig;
function process(input: any) { ... }
// PROPER FIX: Make types truthful
const value: UserProfile | undefined = response.data;
if (!value) throw new Error("Missing user profile");
const config: AppConfig = { theme: "light", locale: "en" };
function process(input: UserProfile) { ... }Gate function:
BEFORE using `as`, `any`, `unknown` cast, or `!` (non-null assertion):
Ask: "Why doesn't the type match?"
IF the data shape is genuinely unknown:
Use runtime validation (Schema, Zod, or type guards)
IF the type is wrong:
Fix the type definition
IF the API returns unexpected shape:
Fix the API contract or add a validation layer
NEVER use type assertions to bypass compiler errorsCategory 2 — LINT: Lint and Warning Suppression
The signal being silenced: Static analysis found a real problem.
// WORKAROUND: Shooting the messenger
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const result = fetchData();
// @ts-ignore
someFunction(wrongArgs);
// @ts-expect-error - TODO fix later
brokenCall();
// PROPER FIX: Fix what the linter found
fetchData(); // Remove unused binding
someFunction(correctArgs); // Fix the argumentsGate function:
BEFORE adding eslint-disable, @ts-ignore, @ts-expect-error, or any suppression:
Ask: "What rule is being violated and WHY?"
IF the code genuinely violates the rule:
Fix the code, not the linter
IF the rule is wrong for this codebase:
Disable the rule in config (globally), not inline
IF it's a third-party type issue:
File an issue, add a minimal typed wrapper
NEVER suppress a warning without understanding itCategory 3 — SWALLOW: Error Swallowing
The signal being silenced: Something failed and the code pretends it didn't.
// WORKAROUND: Pretending errors don't exist
try {
await saveData(payload);
} catch {
// silently ignore
}
try {
result = JSON.parse(input);
} catch {
result = {}; // default to empty - hides corrupt data
}
// PROPER FIX: Handle errors meaningfully
try {
await saveData(payload);
} catch (error) {
logger.error("Failed to save data", { error, payload });
throw new SaveError("Data save failed", { cause: error });
}
const parsed = Schema.decodeUnknownSync(PayloadSchema)(input);
// Throws descriptive error if input is invalidGate function:
BEFORE writing a catch block:
Ask: "What specific errors can occur here?"
Ask: "What should happen when each error occurs?"
IF the answer is "ignore it":
STOP — Ignoring errors hides bugs
IF the answer is "log it":
Log AND propagate or handle meaningfully
IF the answer is "use a default":
Ensure the default is SAFE and the failure is LOGGED
NEVER write an empty catch block
NEVER catch Exception/Error broadly without re-throwing specific typesCategory 4 — TIMING: Timing and Lifecycle Hacks
The signal being silenced: Code runs in the wrong order or at the wrong time.
// WORKAROUND: Racing against the clock
setTimeout(() => {
element.focus();
}, 100);
await new Promise((resolve) => setTimeout(resolve, 500));
// "wait for state to settle"
await retry(() => checkCondition(), { times: 10, delay: 200 });
// retry loop hiding a race condition
// PROPER FIX: Fix the lifecycle
// Use framework-native lifecycle hooks
useEffect(() => {
if (ref.current) ref.current.focus();
}, [isVisible]);
// Use proper event-driven coordination
await waitForEvent(emitter, "ready");
// Use condition-based polling (not blind retries)
await waitUntil(() => service.isReady(), {
timeout: 5000,
message: "Service failed to become ready",
});Gate function:
BEFORE adding setTimeout, delay, sleep, or retry loops:
Ask: "WHY is the timing wrong?"
Ask: "What event signals that the system is ready?"
IF there's an event or callback available:
Use it instead of arbitrary delays
IF the ordering is wrong:
Fix the initialization order
IF it's a test timing issue:
Use condition-based waiting, never arbitrary sleeps
NEVER use setTimeout(fn, 0) to "fix" rendering issues
NEVER use arbitrary delays to "wait for things to settle"Category 5 — PATCH: Monkey Patching and Runtime Mutation
The signal being silenced: The API doesn't do what the code needs.
// WORKAROUND: Mutating things you don't own
Array.prototype.customMethod = function () { ... };
Object.defineProperty(window, "fetch", { value: customFetch });
library.internals._privateMethod = replacement;
// PROPER FIX: Composition over mutation
function customOperation<T>(arr: T[]): T[] { ... }
const wrappedFetch = createFetchWrapper(window.fetch);
const adapter = new LibraryAdapter(library);Gate function:
BEFORE modifying prototypes, globals, or third-party internals:
Ask: "Does the library provide an extension point?"
IF yes: Use the official extension mechanism
IF no: Wrap with composition/adapter pattern
IF the library is broken: File issue, fork, or find alternative
NEVER modify objects the code doesn't ownCategory 6 — SCATTER: Defensive Duplication
The signal being silenced: The data is unreliable at its source.
// WORKAROUND: Checking everywhere because source is broken
function renderUser(user: User) {
const name = user?.name ?? user?.displayName ?? "Unknown";
const email = user?.email ?? user?.contacts?.email ?? "";
const id = user?.id ?? user?.userId ?? user?._id ?? "";
// ... 20 more optional chains
}
// PROPER FIX: Validate once at the boundary
const user = Schema.decodeUnknownSync(UserSchema)(rawData);
// user is now guaranteed to have correct shape
function renderUser(user: User) {
// No defensive checks needed — schema validated at entry
return `${user.name} (${user.email})`;
}Gate function:
BEFORE adding optional chaining (?.) or nullish coalescing (??) deeply:
Ask: "Why might this value be missing?"
Ask: "Where does this data enter the system?"
IF data is unvalidated at entry:
Add validation at the boundary, remove defensive checks downstream
IF the type allows undefined but shouldn't:
Fix the type to be non-optional
IF it's truly optional:
Handle the None/undefined case explicitly at the nearest decision point
NEVER scatter optional chains as a substitute for proper validationCategory 7 — CLONE: Copy-Paste Adaptation
The signal being silenced: The abstraction doesn't fit but the developer forces it.
// WORKAROUND: Copy and "adapt" (badly)
// Copied from UserService and changed 3 lines
function createProject(data: ProjectData) {
// 200 lines, 95% identical to createUser
// but with subtle bugs from incomplete adaptation
}
// PROPER FIX: Extract shared pattern or write fresh
// Option A: Extract the common pattern
function createEntity<T>(schema: Schema<T>, repo: Repository<T>) {
return (data: T) => pipe(
Schema.decode(schema)(data),
Effect.flatMap(repo.insert),
);
}
// Option B: Write purpose-built code
function createProject(data: ProjectData) {
// Clean, focused implementation for projects
}Gate function:
BEFORE copying code and modifying it:
Ask: "Am I copying because the pattern is the same or because I'm lazy?"
IF the pattern is genuinely the same:
Extract a shared abstraction first, then use it
IF the pattern is similar but different:
Write purpose-built code — similar-looking code with different intent
should NOT be forced into the same abstraction
NEVER copy-paste more than 5 lines without questioning whyThe Compound Cost
A workaround that saves 30 minutes today costs 30 hours when copied to 5 places, debugged 3 times, and confused 4 developers over 6 months. The interest rate on workarounds is predatory.
Red Flags — STOP and Rethink
Catch these thought patterns and STOP:
| Thought | What It Means |
|---|---|
"Just add as any to make it compile" | TYPE — Type system evasion |
| "Disable the lint rule for this line" | LINT — Warning suppression |
| "Wrap it in try-catch and ignore the error" | SWALLOW — Error swallowing |
| "Add a setTimeout to fix the timing" | TIMING — Lifecycle hack |
| "Override the prototype/global" | PATCH — Monkey patching |
"Add ?. everywhere just to be safe" | SCATTER — Defensive duplication |
| "Copy this code and change a few things" | CLONE — Copy-paste adaptation |
| "It works, don't touch it" | Fear masking a fragile workaround |
| "We'll fix it properly later" | Later never comes |
| "It's just temporary" | Nothing is more permanent |
The Escape Valve
Not every problem can be fixed at root cause. When a workaround is genuinely unavoidable:
REQUIRED conditions (ALL must be true):
1. Root cause is in external code the team does not control
2. The proper fix requires upstream changes with uncertain timeline
3. The business impact of NOT shipping exceeds the technical debt cost
4. The workaround is ISOLATED (does not leak into other code)
IF all conditions are met:
1. Mark with explicit comment: // WORKAROUND: [reason] — see [issue-link]
2. File a tracking issue for removal
3. Add a test that verifies the workaround behavior
4. Add a test that will FAIL when the upstream fix lands (canary test)
5. Set a review date (max 90 days)
IF any condition is NOT met:
Fix the root cause. No exceptions.Common Rationalizations
| Excuse | Reality |
|---|---|
| "It's just a small workaround" | Small workarounds become big patterns when copied |
| "We don't have time for the proper fix" | Workarounds cost MORE time in debugging and maintenance |
| "The type system is too strict" | The type system found a real bug — listen to it |
| "Nobody will copy this" | Every workaround in a codebase gets copied within 3 months |
| "It's behind a feature flag" | Feature flags don't expire — the workaround becomes permanent |
| "The test passes" | A passing test with a workaround tests the workaround, not the code |
| "I'll create a tech debt ticket" | 93% of tech debt tickets are never resolved |
| "The external library forces this" | Use The Escape Valve process above, with all 5 requirements |
The Bottom Line
Every workaround is a bet that nobody will ever need to understand this code again.
That bet always loses.
Fix the disease, not the symptom.
Fix the source, not the signal.
Fix the code, not the compiler message.For the detailed catalog of 30+ specific workaround patterns with before/after code: Read references/workaround-catalog.md.
For the philosophical and engineering foundations: Read references/philosophical-foundations.md.
Philosophical Foundations
The "no workarounds" principle is not new. It is the convergence of decades of engineering wisdom from manufacturing, software craftsmanship, and systems thinking. These are the intellectual roots.
1. Toyota's Jidoka — "Stop and Fix"
Origin: Sakichi Toyoda invented a loom that automatically stopped when a thread broke, preventing defective fabric from being produced. Taiichi Ohno, architect of the Toyota Production System, generalized this into the Jidoka principle.
The principle: When a defect is detected, STOP the line immediately. Do not pass the defect downstream. Fix the root cause before resuming production.
Ohno's quote: "No problem discovered when stopping the line should wait longer than tomorrow morning to be fixed."
Why it matters for software: Jeffrey Liker and David Meier identified that "the decision to stop and fix problems as they occur rather than pushing them down the line to be resolved later" is a large part of the difference between Toyota's effectiveness and other companies who tried to adopt lean manufacturing.
The anti-pattern: GM's Fremont plant never stopped the assembly line, no matter what. Quality problems were pushed downstream, creating massive rework costs. Toyota stops immediately, and after a few months of ramp-up, their lines run far more reliably.
Software translation: A workaround is pushing a defect down the line. It "works" in the moment but creates downstream rework, debugging sessions, and compound failures. Stopping to fix the root cause feels slower but produces vastly better outcomes.
2. The Broken Windows Theory
Origin: James Q. Wilson and George L. Kelling (1982) observed that a single broken window in a building, left unrepaired, signals that nobody cares — leading to more broken windows, then vandalism, then serious crime.
Application to software: Andrew Hunt and Dave Thomas popularized this in "The Pragmatic Programmer" (1999):
"Don't live with broken windows. Fix each one as soon as it is discovered. If there is insufficient time to fix it properly, then board it up."
The principle: One workaround in a codebase signals that workarounds are acceptable. The next developer sees it and thinks "this is how things are done here." Within months, workarounds spread through the codebase like decay.
Why it matters: A single // @ts-ignore or as any in a codebase gives implicit permission for every developer to add their own. The cost is not the individual workaround — it's the culture of workarounds it creates.
3. Martin Fowler's Technical Debt Quadrant
Origin: Martin Fowler extended Ward Cunningham's debt metaphor into a 2x2 matrix:
| Reckless | Prudent | |
|---|---|---|
| Deliberate | "We don't have time for design" | "We must ship now and deal with consequences" |
| Inadvertent | "What's layering?" | "Now we know how we should have done it" |
The key insight: Most workarounds are Reckless-Deliberate — the team knows it's wrong but does it anyway to save time. This is the most expensive quadrant because it creates a mess, not a strategic debt.
Uncle Bob's distinction: Robert C. Martin argued that "a mess is not a technical debt." A mess is sloppy code written without discipline. Technical debt is a conscious, strategic decision with a plan to repay. Calling a mess "technical debt" is an excuse for poor craftsmanship.
Workarounds are messes, not debts. They have no repayment plan, no tracking, and no expiration date.
4. Linus Torvalds' "Good Taste"
Origin: In his 2016 TED Talk, Torvalds illustrated "good taste" with a linked list example. The "bad taste" approach uses a special case (if statement) to handle removing the first element. The "good taste" approach uses an indirect pointer, eliminating the special case entirely.
The principle: Good taste means writing code that handles edge cases naturally through better design, rather than patching around them with conditional checks.
Applied to workarounds: A workaround is always a special case — an if, a try-catch, a type assertion — bolted onto code that doesn't naturally handle the situation. The proper fix redesigns so the edge case doesn't exist.
5. Kent Beck's "Make It Work, Make It Right, Make It Fast"
Origin: Attributed to Kent Beck (with roots in Unix philosophy from Butler Lampson, 1983):
1. Make it work — Handle one common case 2. Make it right — Handle all cases, refactor 3. Make it fast — Optimize performance
The critical distinction: "Make it work" does NOT mean "make it work with workarounds." It means make the core logic correct for the common case. "Make it right" means fix all edge cases and clean up design. Workarounds skip step 2 entirely — they stay at "make it work" forever.
Beck's TDD formulation: "Write a test, make it run, make it right." Making it run allows temporary violations of good design. Making it right means refactoring immediately — not in a future sprint, not in a tech debt ticket, NOW.
6. Google's Code Health Principles
Origin: Google's internal "Code Health" teams publish tips for maintaining codebase quality. Key principles:
- "Too complex" means "can't be understood quickly by code readers" — Workarounds add complexity that readers must decode.
- Over-engineering is a form of complexity — But so is under-engineering (workarounds instead of proper abstractions).
- Code review exists to improve code health over time — Not to rubber-stamp workarounds.
- YAGNI applies to workarounds too — If the workaround is "temporary," is it needed at all?
Google's zero-warnings approach: Warnings are treated as errors. The build fails on any warning. This eliminates the culture of "it's just a warning."
7. The Software Craftsmanship Movement
Origin: Sandro Mancuso's "The Software Craftsman" (2014) and the Software Craftsmanship Manifesto:
"Not only working software, but also well-crafted software."
"Not only responding to change, but also steadily adding value."
The principle: A craftsperson takes pride in their work. They don't ship work they're not proud of. They don't leave messes for others to clean up.
Applied to workarounds: Every workaround is a failure of craftsmanship. It says "I don't care enough about this code to fix it properly." The craftsman's response to time pressure is not "ship the workaround" — it's "negotiate scope, but never negotiate quality."
8. The Compounding Effect
All seven principles share a common observation: problems caught early cost orders of magnitude less to fix than problems caught late.
| When Caught | Relative Cost |
|---|---|
| During coding (root cause fix) | 1x |
| During code review | 3x |
| During testing | 10x |
| In staging/QA | 30x |
| In production | 100x |
| After users are affected | 1000x |
A workaround defers the cost from 1x (fixing now) to 100x+ (fixing in production after the workaround fails in an unexpected way). The "time saved" by a workaround is borrowed at predatory interest rates.
Summary
These principles converge on one truth:
The fastest way to build software is to build it correctly.
Not perfectly. Not over-engineered. But correctly — with honest types, meaningful error handling, proper lifecycle management, and code that says what it means.
Every workaround is a lie that compounds over time. The no-workarounds principle is simply the practice of telling the truth in code.
Workaround Catalog
A comprehensive catalog of specific workaround patterns, organized by category. Each entry includes the workaround, why it's harmful, and the proper fix.
Type System Evasion
W-01: Blanket any Type
// WORKAROUND
function processData(data: any) {
return data.items.map((item: any) => item.name);
}
// PROPER FIX
interface DataPayload {
items: Array<{ name: string; id: string }>;
}
function processData(data: DataPayload) {
return data.items.map((item) => item.name);
}Harm: Disables all type checking. Runtime errors instead of compile-time errors.
W-02: Type Assertion to Force Compilation
// WORKAROUND
const config = {} as AppConfig;
// Missing required fields — will crash at runtime
// PROPER FIX
const config: AppConfig = {
apiUrl: "https://api.example.com",
timeout: 5000,
retries: 3,
};Harm: as bypasses excess property checks. Missing fields become runtime undefined.
W-03: Non-Null Assertion on Uncertain Values
// WORKAROUND
const user = users.find((u) => u.id === id)!;
// Crashes if user not found
// PROPER FIX
const user = users.find((u) => u.id === id);
if (!user) {
throw new UserNotFoundError(id);
}Harm: ! tells the compiler "trust me" — the compiler should not need to trust.
W-04: Generic object or Record<string, unknown>
// WORKAROUND
function save(data: Record<string, unknown>) {
// No type safety inside
}
// PROPER FIX
function save(data: CreateUserInput) {
// Full type safety, autocomplete, refactoring support
}Harm: Defers type errors to runtime. Impossible to refactor safely.
W-05: Double Assertion (as unknown as T)
// WORKAROUND
const value = response as unknown as TargetType;
// Forces incompatible types to match
// PROPER FIX
// If the types truly don't match, the data needs transformation:
const value = transformResponse(response);
// Where transformResponse handles the actual mappingHarm: The most aggressive type lie. Hides fundamental mismatches in data shapes.
Lint and Warning Suppression
W-06: Inline eslint-disable
// WORKAROUND
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const handler = (e: any) => {};
// PROPER FIX
const handler = (e: React.ChangeEvent<HTMLInputElement>) => {};Harm: Suppresses a specific, useful check. The rule exists because the pattern is harmful.
W-07: @ts-ignore / @ts-expect-error Without Fix Plan
// WORKAROUND
// @ts-ignore
brokenLibraryCall(args);
// PROPER FIX (if library types are wrong)
// Create a typed wrapper:
function safeLibraryCall(args: CorrectArgs): ReturnType {
return (brokenLibraryCall as unknown as CorrectSignature)(args);
}
// The assertion is isolated to ONE place with documentationHarm: Disables type checking for an entire line. Any error on that line is invisible.
W-08: Suppressing Deprecation Warnings
// WORKAROUND
// @ts-expect-error - deprecated but still works
oldApi.legacyMethod();
// PROPER FIX
newApi.currentMethod(); // Migrate to the replacementHarm: Deprecated APIs are removed in future versions. The workaround defers a breaking change.
Error Swallowing
W-09: Empty Catch Block
// WORKAROUND
try {
await riskyOperation();
} catch {
// nothing
}
// PROPER FIX
try {
await riskyOperation();
} catch (error) {
logger.error("riskyOperation failed", { error });
// Either: re-throw, return error type, or handle specifically
}Harm: The most dangerous anti-pattern. Makes failures invisible. Debugging becomes impossible.
W-10: Catch-and-Default
// WORKAROUND
let config: Config;
try {
config = loadConfig();
} catch {
config = defaultConfig; // Hides broken config file
}
// PROPER FIX
let config: Config;
try {
config = loadConfig();
} catch (error) {
logger.warn("Config load failed, using defaults", { error });
// OR: throw new ConfigError("Cannot load config", { cause: error });
config = defaultConfig;
}Harm: Silently uses defaults when the real config is corrupt. Behavior diverges from intent.
W-11: Overly Broad Catch
// WORKAROUND
try {
processOrder(order);
} catch (error) {
// Catches EVERYTHING: network, validation, bugs, OOM
return { success: false };
}
// PROPER FIX
try {
processOrder(order);
} catch (error) {
if (error instanceof ValidationError) {
return { success: false, errors: error.details };
}
if (error instanceof NetworkError) {
return { success: false, retry: true };
}
throw error; // Unknown errors bubble up
}Harm: Treats bugs the same as expected errors. Programming mistakes become "handled."
W-12: .catch(() => null) on Promises
// WORKAROUND
const data = await fetchUser(id).catch(() => null);
if (!data) return; // Was it a 404? A network error? A bug?
// PROPER FIX
try {
const data = await fetchUser(id);
return processUser(data);
} catch (error) {
if (error instanceof NotFoundError) return null;
throw error;
}Harm: Collapses all failure modes into null. Impossible to distinguish errors from empty results.
Timing and Lifecycle Hacks
W-13: setTimeout(fn, 0) for Render Timing
// WORKAROUND
useEffect(() => {
setTimeout(() => {
ref.current?.focus();
}, 0);
}, []);
// PROPER FIX
useEffect(() => {
if (ref.current) ref.current.focus();
}, [isReady]); // Depend on actual readiness, not timingHarm: Creates race condition. Works "most of the time" but fails under load or slow devices.
W-14: Arbitrary Sleep in Tests
// WORKAROUND
test("data loads", async () => {
render(<DataView />);
await new Promise((r) => setTimeout(r, 500));
expect(screen.getByText("Loaded")).toBeInTheDocument();
});
// PROPER FIX
test("data loads", async () => {
render(<DataView />);
await waitFor(() => {
expect(screen.getByText("Loaded")).toBeInTheDocument();
});
});Harm: Flaky tests. Passes on fast machines, fails on CI. Or wastes time on slow fixed delays.
W-15: Retry Loops Hiding Race Conditions
// WORKAROUND
async function waitForReady() {
for (let i = 0; i < 10; i++) {
if (await checkReady()) return;
await sleep(200);
}
throw new Error("Timeout");
}
// PROPER FIX
async function waitForReady() {
return new Promise<void>((resolve, reject) => {
const timeout = setTimeout(() => reject(new Error("Timeout")), 5000);
service.on("ready", () => {
clearTimeout(timeout);
resolve();
});
});
}Harm: Polling wastes resources and still has timing windows where the condition is missed.
Monkey Patching and Runtime Mutation
W-16: Prototype Extension
// WORKAROUND
Array.prototype.last = function () {
return this[this.length - 1];
};
// PROPER FIX
import { last } from "es-toolkit";
const lastItem = last(items);Harm: Pollutes global namespace. Conflicts with other libraries. Breaks for...in loops.
W-17: Global State Override
// WORKAROUND
window.__APP_CONFIG__ = { debug: true };
// PROPER FIX
const config = createConfig({ debug: isDevelopment() });
// Pass through dependency injection or contextHarm: Global mutable state is the root of all evil in concurrent/async systems.
W-18: Replacing Library Internals
// WORKAROUND
router._routes.push(customRoute);
// PROPER FIX
router.addRoute(customRoute); // Use official API
// Or: extend via plugin/middleware systemHarm: Breaks on library updates. Internal APIs change without notice.
Defensive Duplication
W-19: Optional Chaining Everywhere
// WORKAROUND
const name = data?.user?.profile?.name ?? "Unknown";
const email = data?.user?.contacts?.primary?.email ?? "";
const avatar = data?.user?.profile?.images?.avatar?.url ?? "/default.png";
// PROPER FIX — validate at entry point
const user = Schema.decodeUnknownSync(UserSchema)(data);
// Then use with confidence:
const { name, email, avatarUrl } = user;Harm: Every ?. is an implicit admission that the data shape is unreliable. Fix the shape.
W-20: Fallback Chains
// WORKAROUND
const id = item.id ?? item._id ?? item.uuid ?? item.key ?? generateId();
// PROPER FIX — normalize at ingestion
interface NormalizedItem {
id: string;
// ... other fields
}
function normalizeItem(raw: ExternalItem): NormalizedItem {
return { id: raw.id ?? raw._id ?? raw.uuid, ... };
}
// Normalize ONCE, use clean data everywhereHarm: Fallback logic duplicated across the codebase. Each site may have different fallback order.
Copy-Paste Adaptation
W-21: Copied Handler with Tweaks
// WORKAROUND — copied from handleUserCreate and "adapted"
async function handleProjectCreate(req: Request) {
// 150 lines, 90% identical to handleUserCreate
// 3 subtle bugs from incomplete adaptation
}
// PROPER FIX — extract shared pattern
const handleUserCreate = createEntityHandler(UserSchema, userRepo);
const handleProjectCreate = createEntityHandler(ProjectSchema, projectRepo);Harm: When a bug is fixed in the original, the copy is forgotten. Bugs diverge.
W-22: Duplicated Validation Logic
// WORKAROUND — same validation in 5 places
if (!email || !email.includes("@") || email.length > 255) { ... }
// Repeated in: signup, profile update, invitation, import, admin
// PROPER FIX — single source of truth
const EmailSchema = Schema.String.pipe(
Schema.pattern(/^[^@]+@[^@]+\.[^@]+$/),
Schema.maxLength(255),
Schema.brand("Email"),
);
// Use EmailSchema everywhereHarm: Validation rules drift. One place allows 255 chars, another 320. Inconsistent behavior.
Environment and Build Hacks
W-23: Environment Variable as Feature Flag
// WORKAROUND
if (process.env.SKIP_VALIDATION === "true") {
return data; // Skip validation in "problematic" environments
}
// PROPER FIX
// Fix the validation. If it's too slow, optimize it.
// If it catches real errors, those errors need fixing.
// Environment-specific behavior bypasses create parity gaps.Harm: Creates divergence between environments. Bugs that only appear in production.
W-24: Build Script Workarounds
# WORKAROUND
npm run build || true # Ignore build errors
npm run build 2>/dev/null # Suppress error output
# PROPER FIX
npm run build # Fix the build errorsHarm: Ships broken code. Errors are invisible. Failures compound silently.
Test-Specific Workarounds
W-25: Test-Only Methods in Production Code
// WORKAROUND
class Database {
// Only used in tests!
_resetForTesting() {
this.connections = [];
this.cache.clear();
}
}
// PROPER FIX — test utilities separate from production
// In test-utils/database.ts:
export function resetDatabase(db: Database) {
// Use public API or test-specific setup
}Harm: Production code polluted with test concerns. Dangerous if accidentally called.
W-26: Mocking to Avoid Understanding
// WORKAROUND — mock everything, test nothing
vi.mock("./database");
vi.mock("./auth");
vi.mock("./logger");
vi.mock("./cache");
test("it works", () => {
expect(true).toBe(true); // What are we testing?
});
// PROPER FIX — mock minimally, test behavior
vi.mock("./database"); // Only mock the external boundary
test("creates user in database", async () => {
const result = await createUser(validInput);
expect(database.insert).toHaveBeenCalledWith(expectedRecord);
});Harm: Tests pass but verify nothing. False confidence. See test-anti-patterns skill.
W-27: Skipped Tests as "TODO"
// WORKAROUND
test.skip("handles concurrent updates", () => {
// TODO: fix this test
});
// PROPER FIX
// Either fix the test NOW or delete it and file an issue.
// A skipped test is a lie — it implies coverage that doesn't exist.Harm: Skipped tests decay. The code they were meant to protect changes without verification.
Architecture Workarounds
W-28: God Object / Utility Dumping Ground
// WORKAROUND
// utils.ts — 2000 lines, 47 unrelated functions
export function formatDate() { ... }
export function validateEmail() { ... }
export function calculateTax() { ... }
export function parseMarkdown() { ... }
// PROPER FIX
// domain-specific modules
// date-formatting.ts, email-validation.ts, tax-calculator.ts, etc.Harm: Barrel file imports, circular dependencies, impossible to tree-shake.
W-29: Props Drilling Instead of Proper State
// WORKAROUND — passing props through 6 levels
<App user={user}>
<Layout user={user}>
<Sidebar user={user}>
<Nav user={user}>
<UserBadge user={user} />
// PROPER FIX — context or state management
const UserContext = createContext<User | null>(null);
// Or: Zustand store, or TanStack Query cacheHarm: Every intermediate component re-renders on user change. Refactoring is painful.
W-30: Feature Flags That Never Expire
// WORKAROUND
if (featureFlags.newCheckout) {
// "new" checkout — shipped 18 months ago
return <NewCheckout />;
}
return <OldCheckout />; // Dead code? Or still used?
// PROPER FIX
// Remove the flag and the old code after rollout is confirmed.
return <Checkout />;Harm: Dead code branches accumulate. Nobody knows which flags are active. Testing surface doubles.
The Pattern
Every workaround follows the same structure:
1. A signal appears (compiler error, test failure, runtime crash) 2. The workaround silences the signal (type assertion, try-catch, delay) 3. The underlying problem remains and worsens over time 4. The workaround gets copied by developers who see it as precedent 5. The compound cost exceeds what the proper fix would have cost by 10-100x
Break the pattern: Fix the signal source. Never silence the signal.