← Writing
// lessons

Every lesson

398 lessons. Find a subject, or follow the days.

398 lessons · grouped by category · newest first in each category

Software engineering

81 lessons

  1. Software engineering

    A fix brief sets the width of the fix.

    Brief a worker on one failing value and it may repair only that value. Brief it on the broken rule and its input dimensions so the next sibling failure is part of the same job.

    How it works

    The problem

    A review finds a defect, the patch passes, and the next review finds a neighbouring input that reaches the same cause. The work keeps returning because each brief names only the latest example.

    Why it happens

    An example establishes that a rule is broken. It does not define the full boundary of that rule. A worker asked to make one example pass can reasonably stop before inspecting the other callers, formats or input states that share the mechanism.

    How to work through it

    1. Reproduce the finding, then write the broken rule in one sentence. Describe the mechanism rather than repeating the failing value.
    2. List the relevant input dimensions: for a parser, empty, malformed and valid input are different cases. Search the shared code and its callers, and bound the repair by the behaviour this change promises.
    3. Brief the repair around that class, with the original example as one row in a test table. Ask the reviewer to derive a sibling case independently.

    The solution

    Give the worker a cause, a bounded class and a check for that class. One shared repair may cover several findings; unrelated old defects still need a recorded disposition.

    How to check it

    For a future fix, confirm that the original case fails before the patch and passes after it, then test another combination that was not named in the report. A sibling failure means the claimed boundary needs another look.

    Where this stops

    The source reports repeated narrow rounds and supplies the wider briefing method. It does not prove that every bug needs a broad rewrite: a genuinely isolated wrong value can justify a one-off correction.

  2. Software engineering

    A snapshot id must cover its immutable metadata.

    If two records share an id but differ in fields the store promises never to change, an ordinary second write becomes a collision. Decide what the identity owns before hashing it.

    How it works

    The problem

    The same unchanged source can be selected for two jobs: as a reference in one task and as a rule in another. If both selections receive the same snapshot id while their stored metadata differs, an append-only store has to reject the second one.

    Why it happens

    The id and the immutability promise describe different objects. Hashing the source bytes alone identifies those bytes. It does not identify a record that also freezes the source role, area or other metadata.

    How to work through it

    1. List every field that is immutable in the stored snapshot. Separate source facts from the reason a particular task selected it.
    2. Either include all immutable record fields in its identity, or keep the snapshot independent of the task and move task-specific selection metadata into the receipt.
    3. Test selecting the same source in two roles, then repeat both writes. Both legitimate selections should work, and a replay should keep the same ids and bytes.

    The solution

    Make the identity boundary match the record boundary. A refusal on changed bytes is useful; a refusal on two supported ways of selecting the same source reveals a mismatch in the design.

    How to check it

    The source review reports that its original two-role reproduction succeeded after the identity was expanded. For another store, replay that sequence and also confirm that a changed immutable field cannot silently replace the old record.

    Where this stops

    The review establishes its recorded reproduction and recheck, not every downstream use. Content hashes do not authenticate the writer or prove the source is true.

  3. Software engineering

    A fake binary on PATH can lose to an inherited environment variable.

    The tests installed a fake aws command, but the launcher had already selected a real binary through AWS_CLI_BIN.

    How it works

    The problem

    A test puts a fake aws command on PATH and expects all calls to reach it. That expectation fails if the application also accepts an explicit binary path from its environment. The fake can be present and executable while never being called.

    Why it happens

    The tests inherited AWS_CLI_BIN from the session launcher. That explicit binary selection took precedence over the fake command on PATH. Plain shells and CI did not set the variable, so their successful runs missed this route.

    How to work through it

    1. Check how the command chooses its executable, including explicit environment overrides.
    2. Remove the inherited override in the default test fixture. Let tests that need it set their own value.
    3. Start a nested test run with the variable deliberately set and confirm the fixture still removes it.

    The solution

    Make the test environment explicit at the suite boundary. A fake on PATH only covers callers that use PATH, so test the launch environment that can override it too. A harmless worked example can use two toy executables that print different sentinel words: set the override to one and PATH to the other, then check which word appears.

    How to check it

    The source reports a nested-test regression that failed without the fixture and passing CI after the fix. The toy sentinel example is a suggested reproduction, not a run performed for this post.

    Where this stops

    This closes the recorded AWS_CLI_BIN leak. Clearing one variable does not make live cloud access impossible; other credential or executable-selection routes need their own isolation.

  4. Software engineering

    A command should tell the operator what to do next.

    Every make command became a small guide: explain the fix when it fails, or show the follow-on commands when it succeeds.

    How it works

    The problem

    An operator runs a command, gets a result and still has to search the documentation to work out the next action. Knowing the exit code does not explain how to recover or continue.

    Why it happens

    The commands handled their own work, but the route between them lived elsewhere. Advice printed in individual scripts could also drift away from the targets and arguments that actually existed.

    How to work through it

    1. Write down the command routes: what can follow success, what can recover a failure and where the flow finishes.
    2. Print a short Next block at command exit. Say what each suggested command does.
    3. Check the routes in CI: targets exist, nodes are reachable, paths can finish and suggested arguments are read by the next target.

    The solution

    Keep command navigation in a checked graph and let the command print the relevant part. Retry advice should carry values the operator typed, while asking for confirmation again when the action needs it.

    How to check it

    The work began on October 22 with a spec, plan and graph checker. Final review fixes merged on October 23, including cancellation handling and argument-carry checks.

    Where this stops

    The final changes were checked on the host, but the container walk was not rerun for that final PR. A checked command graph does not prove every live operating environment.

  5. Software engineering

    A green test can depend on a file Git never tracked.

    A clean worktree exposed a bug that the main checkout had been hiding with an untracked symlink.

    How it works

    The problem

    The full verification run failed in a clean worktree: a separate checkout containing the committed files, without my local extras. The familiar checkout had looked healthy. The new changes seemed the obvious cause, but the same failure appeared on the base revision.

    Why it happens

    An untracked symlink, a file pointing to another location, made the test take an easier path in the main checkout. Without that local shortcut, the audit and update checks followed the failing path. A second failure came from inherited make settings changing the command output.

    How to work through it

    1. Run the failing test in clean checkouts of the base revision and each changed revision.
    2. Compare the tracked and untracked state. Add back one suspected file and check whether it changes the result.
    3. Clear inherited runner settings when testing output, then rerun through the original failing route.

    The solution

    Put the state a test needs in its fixture. Keep an independent clean-checkout run in the verification flow so my working directory cannot quietly supply missing setup.

    How to check it

    The investigation reproduced the failure on clean origin. Adding one untracked file made that test pass. Clearing inherited make settings also made the output test pass.

    Where this stops

    The record opened a fix slice for the fixture, the underlying bug and the output test. It does not establish that the complete fix had landed that day.

  6. Software engineering

    A fake host cannot prove an update leaves a busy real host alone.

    The suite had tested the refusal against fakes. A supervised host pilot checked the behaviour where the update would actually run.

    How it works

    The problem

    An update is supposed to stop when the application is busy. A passing test tells me the check works in the environment the test created. I still need to know what the real host does, including whether a refused update has already changed anything.

    Why it happens

    Fakes leave out parts of the installed system. The real pilot found an old loader and a rotated-log naming case that the existing checks had missed.

    How to work through it

    1. Keep the application open and record the installed state before starting the update.
    2. Run the update in a supervised terminal. Capture its message and exit code, then compare the state afterwards.
    3. Separate a product defect from a host configuration fact. Reproduce a defect before changing code.

    The solution

    Make refusal a behaviour I can demonstrate: the command explains why it stopped, returns the expected code and leaves the installation alone. Keep a short evidence record so the next person can repeat the same check.

    How to check it

    The pilot reported BUSY, returned exit 4 and changed nothing while the application was open. Host report values were also compared with operating-system tools.

    Where this stops

    This was a supervised pilot. Dashboard and fleet-path steps were not run, so it did not prove the whole deployment flow.

  7. Software engineering

    If setup is good, CI should just call it.

    Then every CI run tests the real bootstrap on a fresh machine, and the setup steps can't quietly rot.

    How it works

    The problem

    A repository can pass CI while its documented setup fails for a new developer. This happens when the pipeline installs dependencies through a separate path that nobody maintains as the public bootstrap.

    Why it happens

    Two setup scripts create two descriptions of the required environment. The familiar CI path stays exercised while the developer path quietly misses a tool, configuration step or version constraint.

    How to work through it

    1. Make the documented setup command the entry point for both developers and fresh CI workers. Let environment-specific choices be explicit parameters rather than a second handwritten installation sequence.
    2. Run the normal checks after that setup on a clean workspace. Cache downloads where useful, but keep a check that does not depend on a pre-existing virtual environment or undeclared host package.

    The solution

    Use CI to rehearse the path a new contributor follows. Fixing one bootstrap then repairs both environments and makes the documentation easier to trust.

    How to check it

    Try the documented command in a clean supported environment and inspect failures for missing prerequisites. A successful check should require only the prerequisites the documentation actually names.

    Where this stops

    Hosted CI covers its own operating system and network conditions. It cannot prove every developer platform works, and caches or preinstalled tools can still mask a missing setup step.

  8. Software engineering

    Make the design defend itself.

    Take every feature it must carry and ask the hard questions: ten times the input, a second user, a failure nobody notices. Gaps found now cost an edit.

    How it works

    The problem

    A design document can describe every component and still fail to explain how the system handles its required features. The missing part appears when a second user, larger input or unnoticed failure reaches the design.

    Why it happens

    A component list shows what exists. A defence question follows one feature through load, ownership and failure paths, forcing the document to explain how those pieces work together.

    How to work through it

    1. List each feature the design must carry and ask a few hard questions on its behalf. Include scale, concurrency, a broken dependency and who notices when the result is wrong.
    2. Answer from the design as written, linking the section that provides the mechanism. Mark an unanswered question as a gap, then change the design or name the scope cut before planning the build.

    The solution

    Use the defence record to turn broad confidence into specific claims that can be challenged. Repeat the exercise when a new feature changes what the design must support.

    How to check it

    Give the record to a reader who did not write the document. They should be able to follow the mechanism and find a concrete answer without relying on the author's spoken explanation.

    Where this stops

    The quality of the questions still needs judgement. A complete table is useful coverage evidence, but it cannot prove that all important failure modes have been imagined.

  9. Software engineering

    Requirements and architecture shape each other.

    A design choice shows a requirement is vague or too costly; a requirement shows a missing block. Write them together, in short loops.

    How it works

    The problem

    Finishing all requirements before exploring any architecture can hide an impossible or needlessly expensive demand. Designing first can hide the opposite problem: a neat system with no answer to an actual need.

    Why it happens

    Requirements constrain the design, while design choices expose missing detail and derived obligations. Working in only one direction delays that feedback until changes have more dependencies.

    How to work through it

    1. Write a small set of goals and a rough architecture together. Allocate each requirement across the parts that contribute to it, identifying the budget or interface each part must meet.
    2. Use what the allocation reveals to refine the requirement or change the design. Record the trade-off, then repeat the loop at the next useful level of detail.

    The solution

    Keep intent and mechanism in conversation. For an illustrative latency target, divide the budget across processing and transport; if one part cannot meet its share, revisit the allocation or target explicitly.

    How to check it

    Check that each important requirement has a design path and that each major design block has a reason. An infeasible budget should appear as an open decision, not a quietly omitted number.

    Where this stops

    A repeated, well-understood design may need less exploration. The source's research model was not independently checked against its cited paper, so this lesson presents the working method rather than a research finding.

  10. Software engineering

    Every branch ends with an exit code and a sign.

    Think of code as a tree of roads. Every dead end should close with a clear status and a message saying why.

    How it works

    The problem

    A command that stops without a clear status forces its caller to guess whether work succeeded. A message alone is also weak evidence if automation can only see an exit code of zero.

    Why it happens

    Each branch in control flow ends in a result, whether success, refusal or failure. If those endings are implicit, an unhandled path can look like an ordinary completion.

    How to work through it

    1. List the terminal outcomes of the operation and assign documented statuses. Keep successful empty results distinct from missing input, invalid configuration and failed work.
    2. Route each terminal branch through the appropriate return or exit path, with a short message that explains the reason. Preserve the status through wrappers and pipelines.

    The solution

    Make the program's outcome usable by both a person and another program. The status selects the caller's next action; the message explains what the caller can do about it.

    How to check it

    Exercise one isolated input for each outcome. Check the structured result or exit status, and check wording only where the message itself is a public contract.

    Where this stops

    A library usually returns a value or raises a typed exception rather than exiting the process. The same principle applies, but the boundary decides which mechanism is appropriate.

  11. Software engineering

    A build cache skips work. It doesn't speed up one long step.

    EDA regressions spend their time in single long tool runs, and caching only helps once every input is declared.

    How it works

    The problem

    A slow regression is not automatically a caching problem. If most time sits inside one long tool invocation, a new build scheduler cannot make that invocation finish faster.

    Why it happens

    Incremental build tools avoid actions whose declared inputs have not changed. They save repeated work, while hung jobs, slow first runs and hidden dependencies need different corrections.

    How to work through it

    1. Break elapsed time into useful execution, waiting, failed work and repeated unchanged work. Compare input revisions between repeated runs rather than treating every repeated job name as a cache opportunity.
    2. Trial a small representative flow with declared source, tool and configuration inputs. Measure cold and warm runs separately and inspect outputs for deterministic behaviour.

    The solution

    Choose the intervention from the measured loss. Time limits and wrapper fixes may matter more than caching when jobs fail to terminate or hold allocations after useful work ends.

    How to check it

    Compare total elapsed and compute time before and after the trial. A credible cache benefit comes from avoided unchanged actions, not a claim that the longest action itself became faster.

    Where this stops

    Undeclared absolute-path reads, tool versions and nondeterministic outputs can make caching unsafe. The historical investigation remained in progress; it did not establish that a new build system was adopted.

  12. Software engineering

    One shared defines header defeats incremental builds.

    When every file includes a hub header, touching it invalidates everything. Skip-based tools like Bazel or Snakemake save little until such hub files are split.

    How it works

    The problem

    An incremental flow may rebuild almost everything after a tiny edit to a common header. A sophisticated scheduler still sees a legitimate dependency change across the whole project.

    Why it happens

    When many source files include one definitions hub, they all depend on its bytes. A change to an unrelated definition invalidates every consumer because the dependency is tracked at file granularity.

    How to work through it

    1. Inspect the include graph and measure the actions invalidated by a representative header edit. Separate genuinely shared definitions from values used by only one subsystem.
    2. Split definitions along real ownership boundaries and update includes to depend on the smallest relevant header. Keep a shared layer only for definitions that truly belong to every consumer.

    The solution

    Reduce unnecessary dependency edges before expecting a cache to skip much work. The scheduler can reuse only the independence that the source structure actually expresses.

    How to check it

    Make a local-only definition change in an illustrative trial and inspect the rebuilt action set. Unrelated subsystems should remain reusable, while real consumers still rebuild.

    Where this stops

    Splitting a header can create awkward include cycles or duplicate definitions if ownership is unclear. Shared interface changes should still invalidate broadly, and hidden inputs remain a separate cache risk.

  13. Software engineering

    Offline tests prove the question. Live data proves the answer.

    A mocked page test shows the UI asks the server the right thing. Only live data shows the server answers it correctly.

    How it works

    The problem

    A page test can prove that selecting a filter sends the intended request and still miss a server that returns the wrong rows. The mocked answer only repeats what the test author expected.

    Why it happens

    The browser and server are two halves of one interaction. An offline fixture can check the question the browser asks; it cannot establish how the real query behaves against real data.

    How to work through it

    1. Keep fast offline tests for control state and outgoing request parameters. Cover empty selections, combined filters and categories that must not disappear.
    2. Replay representative requests against a controlled real API and database, using a copy or safe fixture environment. Check returned rows and counts against independently known cases.

    The solution

    Use both layers to locate errors. A bad outgoing parameter points to the page; a correct request with a bad result points to the server or data interpretation.

    How to check it

    Choose a case with included and excluded rows. The live-path check should return the intended set and matching total, not merely a successful status code.

    Where this stops

    Live data changes and can contain sensitive information. Record the dataset boundary, avoid modifying live stores, and do not call an offline replay proof of current server behaviour.

  14. Software engineering

    A surviving mutant can be a stale .pyc.

    After an in-place edit within the same second, Python reused the cached bytecode. Run mutation checks with python -B.

    How it works

    The problem

    A mutation check can appear to tolerate a deliberate bug because the test imported old bytecode. That turns a cache problem into a false conclusion about test quality.

    Why it happens

    Python may validate cached bytecode using source timestamps and size. A rapid same-size edit can therefore be hard to distinguish from the source that produced the cache.

    How to work through it

    1. Run mutation probes in an isolated copy and remove existing bytecode caches before the probe, or direct caches to a fresh location. Confirm that the test imports the mutated source file.
    2. Use python -B to avoid writing more bytecode during the probe, then restore the source and check the working tree. Keep mutation debris out of later tests.

    The solution

    Treat a surviving mutant as something to investigate: first establish which code executed, then judge whether the assertion detected the changed behaviour.

    How to check it

    Make a mutation with a clear expected failure and inspect the imported module path. After the clean-cache run, the relevant test should fail for the planted defect.

    Where this stops

    The short lesson preserves the historical -B advice, but -B suppresses writes; it does not guarantee existing bytecode will not be read. Import paths and already-loaded modules are separate causes to rule out.

  15. Software engineering

    '.venv/' doesn't ignore a symlink.

    A trailing slash in .gitignore matches only directories, so a symlink named .venv slips into 'git add -A'. Write '.venv'.

    How it works

    The problem

    A local environment link can slip into a commit even when .gitignore contains .venv/. The pattern looks right because the link points to a directory, but Git sees a different kind of entry.

    Why it happens

    A trailing slash restricts an ignore pattern to directories. A symbolic link named .venv is a link in the working tree, so a directory-only rule does not cover it.

    How to work through it

    1. Use .venv without the trailing slash when both a directory and a symlink should be ignored. Keep the pattern scoped to the intended repository location.
    2. Check the index before adding everything. If the link is already tracked, remove it from the index deliberately; changing .gitignore alone does not untrack an existing entry.

    The solution

    Write ignore rules for the entry Git records, rather than the type of its target. This keeps local environment layout out of the repository across different developer setups.

    How to check it

    Use git check-ignore -v .venv to identify the matching rule, then inspect the staged names. A local symlink should remain untracked after the normal add command.

    Where this stops

    Ignore patterns are not protection for secrets already committed. A tracked environment entry needs separate cleanup, and other ignore files can affect which pattern wins.

  16. Software engineering

    Chain tests on events, not delays.

    In a simulator, time is exact. On a busy machine, a sleep before the next step is a flaky test that only passes on a quiet day.

    How it works

    The problem

    A test that sleeps before checking the next stage can pass on a quiet machine and fail under load. Increasing the sleep makes the suite slower without proving that the dependency is ready.

    Why it happens

    A wall-clock delay measures elapsed time, while the next step depends on an event. Process scheduling, I/O and startup work can all change the gap between those two things.

    How to work through it

    1. Name the condition that makes the next step valid: a readiness response, completed task, emitted event or fully written artifact. Wait for that condition rather than a guessed delay.
    2. Put a finite deadline around the wait and report the last observed state on timeout. If polling is necessary, bound it and preserve the underlying failure message.

    The solution

    Chain the test on completion evidence so fast machines move on promptly and slow machines get the time they actually need.

    How to check it

    Exercise the test with an intentionally delayed dependency and with one that never becomes ready. It should proceed after real readiness and fail clearly at the deadline when readiness never arrives.

    Where this stops

    Some tests deliberately study timing, and simulator time has different semantics. Even then, distinguish a required duration from a guessed pause used to hide a missing readiness signal.

  17. Software engineering

    urllib has no default timeout.

    A probe can hang forever unless you set one. Also catch HTTPError, which urllib raises for any non-2xx response.

    How it works

    The problem

    A network probe can hang long enough to block the rest of a monitoring cycle.

    Why it happens

    urllib can wait without a useful application timeout unless one is supplied. HTTP error responses also take an exception path, so code that expects only a returned status misses part of normal probe behavior.

    How to work through it

    1. Supply an explicit timeout for the request.
    2. Handle HTTPError as a received HTTP outcome and separate it from transport failure.
    3. Bound the overall probe workflow and report which target failed.

    The solution

    Illustrative: a 503 response is different from a DNS failure or a socket that never responds. A useful probe records those states instead of crashing or waiting indefinitely.

    How to check it

    Use disposable endpoints for success, HTTP failure and delayed response. Expect a classified result within the intended allowance, including a non-success status when the server answers with an error.

    Where this stops

    Some redirects are followed automatically, so the original non-2xx shorthand is too broad. Catch HTTPError for responses that reach the error handler. The timeout is not necessarily a strict total wall-clock deadline across DNS, redirects and reads; a hard deadline needs a surrounding execution and cancellation strategy.

  18. Software engineering

    The design shouldn't care which library you picked.

    If the architecture can be described without naming components, swapping one is cheap. I swap them often.

    How it works

    The problem

    A design becomes expensive to change when its purpose can only be explained by naming today's libraries.

    Why it happens

    Tight coupling lets one component's interface and storage assumptions spread through the pipeline. Replacing that component then changes several unrelated parts of the system.

    How to work through it

    1. Describe the system in terms of roles and information flow.
    2. Define contracts at those boundaries.
    3. Keep component-specific translation inside adapters and check the same contract when swapping an implementation.

    The solution

    Illustrative: 'retrieve evidence, rank it, compose an answer' can survive a search-backend change if each stage has an explicit input and output. Backend-specific response objects crossing every boundary make that swap harder.

    How to check it

    Replace one component with a small alternative in a disposable example. The surrounding stages should continue to satisfy the agreed contract with limited local changes.

    Where this stops

    A clean diagram does not prove that a migration is cheap. Data format changes, performance assumptions and operational dependencies can still require careful transition work.

  19. Software engineering

    One blocking call stalls every async route.

    A subprocess or sync DB call inside `async def` freezes the event loop, health checks included. Push it to a thread.

    How it works

    The problem

    An asynchronous API can stop answering health requests while one handler waits for a synchronous subprocess.

    Why it happens

    async def does not make the work inside it nonblocking. A blocking function occupies the event-loop thread, preventing other coroutines on that loop from advancing.

    How to work through it

    1. Identify synchronous subprocess, database and file calls inside async handlers.
    2. Use an async-native operation or move suitable blocking work to a bounded worker thread.
    3. Keep timeouts and cancellation behavior explicit.

    The solution

    Illustrative: await asyncio.to_thread(blocking_probe) lets the event loop serve another request while the probe waits. An async subprocess API can provide more direct process lifecycle control.

    How to check it

    Run a deliberately slow operation beside a simple health request in a disposable service. The health request should remain responsive while the slow task is still in progress.

    Where this stops

    Thread offloading does not stop the underlying function automatically when its awaiting request is cancelled. CPU-heavy work, thread safety and pool exhaustion need their own design.

  20. Software engineering

    Python truthiness eats zero.

    'exit_code or "not recorded"' turned a clean exit into missing data. Test for None, not for falsy.

    How it works

    The problem

    A clean exit can appear as missing data when a fallback expression treats zero like absence.

    Why it happens

    Python's or operator chooses its fallback for any falsy value. Exit code 0 and elapsed time 0 are valid measurements, so truthiness is the wrong missing-value test.

    How to work through it

    1. Define which sentinel actually means missing.
    2. Use an explicit None check before choosing the fallback.
    3. Carry zero unchanged through the formatter, filter and export.

    The solution

    Illustrative: value if value is not None else 'not recorded' displays an exit code of 0 as zero while still naming an absent record. The distinction changes how a reader interprets the run.

    How to check it

    Exercise None, 0 and a nonzero value through the real rendering path. Expect all three states to remain distinguishable, including a valid zero threshold in filters.

    Where this stops

    Empty strings and false booleans may also be valid in other fields. Choose the missing-value rule from the field's contract rather than replacing every or expression mechanically.

  21. Software engineering

    You can rule something out without knowing the cause.

    I couldn't prove why a host wedged, but I could prove my service wasn't the reason. Label what you measured apart from what you inferred.

    How it works

    The problem

    A diagnosis can overstate its evidence when ruling out one suspect becomes a claim to know the real cause.

    Why it happens

    Measurements support particular exclusions within a time window. They do not automatically establish why the whole host failed, especially when the decisive display or process state was inaccessible.

    How to work through it

    1. Write down the observed symptom and measurement window.
    2. List what each observation can exclude.
    3. Keep the remaining explanation labelled as a hypothesis and name the evidence needed to test it.

    The solution

    Illustrative: low service resource use during the observed window can weaken a resource-exhaustion theory, while an unrelated GUI-lock theory remains unproven.

    How to check it

    Ask whether another reader can trace every exclusion to an observation and every proposed cause to its test. The report should distinguish measured facts from plausible explanations.

    Where this stops

    Negative evidence is bounded by sampling and access. Do not turn 'not observed here' into a claim that a service could never contribute under different conditions.

  22. Software engineering

    Path.exists() can raise.

    On a path you're not allowed to read, it re-raises the permission error. One unreadable file killed whole poll cycles.

  23. Software engineering

    FastAPI ignores parameters you didn't declare.

    An unknown query parameter is dropped and the call still returns 200, so one table quietly ignored a filter its sibling honoured.

    How it works

    The problem

    A filter control can appear to work because its request returns 200 while the server ignores the requested restriction.

    Why it happens

    An undeclared query parameter can be discarded rather than rejected. The route exists and answers successfully, but its result describes a broader population than the interface promises.

    How to work through it

    1. Compare each client filter with the handler's declared parameters.
    2. Use a dataset where applying the filter must change the result.
    3. Check both returned rows and any summary totals against that same population.

    The solution

    Illustrative: a days=30 request should exclude older fixture records. A successful response containing those records reveals that route existence and filter semantics are separate checks.

    How to check it

    Send the same request with two deliberately different filter values. Expect the population to change as specified, and verify that companion tables use the same scope.

    Where this stops

    Some APIs intentionally tolerate unknown parameters. That convention still requires explicit tests for supported filters; a status-only smoke check cannot establish their behavior.

  24. Software engineering

    Rename first, edit second.

    A pure `git mv` commit keeps `git log --follow` working. Mixing content edits into the move can break rename detection.

    How it works

    The problem

    A file move can make useful history harder to follow when the same patch also rewrites its contents.

    Why it happens

    Git detects renames from content similarity rather than storing a permanent move record. A large simultaneous edit can make the old and new files look unrelated.

    How to work through it

    1. Write a move map before reorganizing paths.
    2. Commit the path-only moves with unchanged contents.
    3. Update imports, scripts and documentation in the next change and check their references.

    The solution

    Illustrative: move a script first, then change how it locates its configuration. The intermediate commit gives history tools an unchanged file to match while keeping the functional edit easy to review.

    How to check it

    Run git log --follow on a moved file and check that it reaches the old path. Then exercise path resolution so a clean historical move does not conceal broken runtime wiring.

    Where this stops

    A pure move improves rename detection but does not guarantee every history tool follows every migration. Cross-repository moves require their own history and provenance plan.

  25. Software engineering

    Squash merge undoes a reformat commit.

    A mechanical reformat kept as its own commit protects git blame. Squash it into the feature and blame points at the formatter again.

    How it works

    The problem

    A formatter can obscure the history of a feature even when its changes were carefully separated during development.

    Why it happens

    Squashing combines the mechanical sweep and functional changes into one final commit. Later blame points to that combined change instead of leaving the reformat independently identifiable.

    How to work through it

    1. Commit formatting separately from behavior changes.
    2. Choose a merge method that preserves the useful commit boundary.
    3. Inspect the resulting history before relying on it for future debugging.

    The solution

    Illustrative: if a patch changes one condition and reformats a hundred files, preserve the formatting commit so a reader can identify and skip that mechanical change while tracing the condition.

    How to check it

    Check the final branch history and run blame on representative reformatted lines. The mechanical change should still have its own commit identity after integration.

    Where this stops

    A separate commit does not automatically make blame show the older semantic change; readers may still need to skip it. Repository merge rules can constrain the available strategy.

  26. Software engineering

    Check behaviour on the version you actually run.

    An older pinned binary silently ignored the newer form of a setting. The docs describe the latest; your fleet runs something else.

    How it works

    The problem

    A configuration can match current documentation while the installed binary silently ignores the setting that was meant to enforce it.

    Why it happens

    Policy syntax and supported keys change between versions. Valid JSON or a successful startup does not establish that an older client understands a newer setting form.

    How to work through it

    1. Record the exact client version and consult the corresponding setting contract, including the accepted value type.
    2. Test the intended behaviour on that pinned binary before rolling out the policy, then repeat the compatibility check when either side changes.

    The solution

    For a setting that accepts an array in the deployed version, do not substitute an object because a newer reference shows one. Bind the policy release to its supported client versions.

    How to check it

    Check effective configuration where available and exercise a harmless action the setting should prevent. Expect the pinned client to enforce the decision, not merely accept the file without an error.

    Where this stops

    A version pin is reproducibility, not proof of security or continued support. Keep an upgrade path and test older clients explicitly if they remain permitted in the fleet.

  27. Software engineering

    Python for now. Rust is underrated.

    The AI ecosystem pulls everything to Python today. For systems-level infrastructure, where speed starts to matter, Rust is worth a serious look.

    How it works

    The problem

    A language choice can become an argument about preference before anyone separates the AI ecosystem needs from the infrastructure’s performance constraints.

    Why it happens

    Python has strong library and integration support for AI work. A systems component may have different needs around memory, speed and deployment, so one language need not serve every layer.

    How to work through it

    1. Start with the libraries and integration paths the feature actually requires, using Python where that ecosystem saves substantial work.
    2. Measure the systems bottleneck before considering a bounded Rust component with an explicit interface back to the rest of the application.

    The solution

    Keep the choice reversible. A narrow service or library boundary is easier to compare and replace than a rewrite justified only by a language’s reputation.

    How to check it

    Compare representative workload latency, memory use, build effort and operational complexity. Include the cost of crossing the language boundary rather than timing only the inner loop.

    Where this stops

    This is a stated direction of interest, not a reported benchmark or a decision to rewrite a project. A mature Python implementation may remain the best choice when it already meets the requirements.

  28. Software engineering

    Enforce the standard one level up.

    If the job wrapper adds the metadata every job needs, nobody below has to remember it, and the data comes out clean.

    How it works

    The problem

    A team can ask every job author to include the same metadata and still receive incomplete records, because each script has to remember the rule independently.

    Why it happens

    The requirement is shared, but enforcement sits below the common boundary. Every new caller becomes another opportunity to omit or invent the fields.

    How to work through it

    1. Identify the wrapper or submission boundary that every supported job already crosses.
    2. Have that layer derive common metadata, validate required caller inputs and reject incomplete submissions before the lower-level work begins.

    The solution

    For a job record, the wrapper can attach submission identity and timing consistently while the job supplies only task-specific fields. Define which fields callers may override instead of silently accepting conflicting copies.

    How to check it

    Submit a minimal job through the wrapper and expect a complete standard record. Test a missing required input and an attempted protected-field override; both should produce a clear decision.

    Where this stops

    This works only for paths that cross the boundary. Direct execution needs to be disallowed or explicitly outside the contract, and centrally added metadata still needs a trustworthy source.

  29. Software engineering

    Quote your YAML.

    Bare no, yes, on and off become booleans, and an unquoted id: 42 becomes an int. Identity keys need a type, not a guess.

    How it works

    The problem

    A metadata value that looks like an identifier can arrive in code as a boolean or integer, breaking comparisons and joins that expect text.

    Why it happens

    YAML loaders infer scalar types. Under YAML 1.1-style rules, unquoted yes, no, on and off can become booleans; an unquoted numeric id can become an integer.

    How to work through it

    1. Quote values whose contract is text, such as `id: "42"` and `status: "no"`.
    2. After loading, validate the resulting types against the schema rather than assuming the source’s appearance establishes its meaning.

    The solution

    Treat quoting as part of the serialized contract. A textual id must remain the same type through parsing, validation and export, even if its characters happen to be digits.

    How to check it

    Load a small fixture with the actual application loader and inspect both value and type. Expect the quoted examples to remain strings, and reject an inferred integer where the schema requires a string.

    Where this stops

    Boolean inference differs across YAML versions and loaders. Test the one you ship; quoting text is useful even when a newer loader no longer treats “on” as a boolean.

  30. Software engineering

    Conventions must work for humans and LLMs alike.

    With few programmers, the next reader of your code is often an agent. Written standards make its reading guaranteed rather than lucky.

    How it works

    The problem

    A module can be present in the repository yet absent from the corpus an agent uses to understand it. Human reviewers and agents then see different projects.

    Why it happens

    A written convention helps only if readers can find it and producers follow the machine-readable contract. Missing or malformed module headers can silently remove useful context.

    How to work through it

    1. Define the exact header and exported-symbol contract that the corpus reader accepts, alongside a short human style guide.
    2. Gate the checkable contract and compare corpus coverage with the source scope, while leaving clarity and design judgment to review.

    The solution

    For a public module, require its supported summary field rather than hoping the next agent infers its role from a filename. Make invalid headers visible instead of silently skipping them.

    How to check it

    Use a missing-header example and a malformed summary example. Expect the gate to reject them and corpus checks to expose lost modules; separately ask a reader whether the accepted summary makes sense.

    Where this stops

    The original phrase “reading guaranteed” overstates what standards can do. A gate can establish structure and coverage, not guarantee that a person or model reads or understands it.

  31. Software engineering

    Read text with utf-8-sig.

    A byte-order mark survives a plain utf-8 read and breaks the parser on line one. One codec name saves a confusing SyntaxError.

    How it works

    The problem

    A Python source scanner can reject a valid-looking file on its first character, then lose the file entirely if that parse error is swallowed.

    Why it happens

    A UTF-8 byte-order mark decodes as an extra leading character with the plain utf-8 codec. Passing that text directly to ast.parse can produce a SyntaxError.

    How to work through it

    1. When the accepted input is UTF-8 text with an optional leading BOM, read it with encoding="utf-8-sig".
    2. Pass the resulting text to the parser and report any remaining parse error with the filename rather than treating it as an empty result.

    The solution

    For example, `text = path.read_text(encoding="utf-8-sig")` removes an optional leading BOM before `ast.parse(text)`. Apply the same input rule to every reader of that file kind.

    How to check it

    Prepare equivalent files with and without the BOM and expect the same parsed structure. Include a genuinely invalid file and expect an explicit error, not a missing corpus entry.

    Where this stops

    This codec handles an optional UTF-8 BOM; it does not detect every encoding or fix invalid source. Use it where the input contract is UTF-8, rather than guessing an arbitrary document’s encoding.

  32. Software engineering

    A formatter outside the gate is decoration.

    The format target existed, but nothing ran it in verify or CI. A tool that isn't wired into a gate isn't enforcing anything.

    How it works

    The problem

    A repository can contain a format target while accepting every unformatted change. Its presence looks like enforcement without changing what passes.

    Why it happens

    The command is disconnected from the path that decides whether work is accepted. A tool nobody has to run is an available action, not a gate.

    How to work through it

    1. Add the formatter’s non-writing check mode to the normal verify target and the CI path used for changes.
    2. Pin the tool version, then introduce a small formatting violation on a disposable branch to check that the acceptance path rejects it.

    The solution

    Keep automatic rewriting as a separate convenience command. For example, use a format-check dependency in verify so CI reports the violation rather than silently changing the submitted files.

    How to check it

    The deliberate violation should make the normal gate exit non-zero. After formatting it, the same gate should pass that check and continue to the other required checks.

    Where this stops

    A formatting gate establishes layout consistency, not readable design or correct behaviour. Large mechanical reformats also deserve a separate change so functional edits remain reviewable.

  33. Software engineering

    Everything the tool reads belongs in the cache key.

    Headers pulled in through an include directory were left out of the key, so a stale result was served as current.

    How it works

    The problem

    A build cache can return an old result after a header changes, even though the top-level source file and command are unchanged.

    Why it happens

    An include-directory option names where a tool looks, but the tool also reads files found there. If those bytes are absent from the cache key, different computations can share one identity.

    How to work through it

    1. Enumerate the complete input set, including resolved headers, configuration and other files the tool reads indirectly.
    2. Build the key from their content and the relevant invocation, then version the key format so incomplete old entries become misses.

    The solution

    For a source that includes a header, changing only that header must change the computation’s key. Treat the include path as a search rule, not a substitute for the files selected through it.

    How to check it

    Populate a disposable cache, change an included header and request the result again. Expect a miss and recomputation. An unchanged input set should still be eligible for a hit.

    Where this stops

    A sound key depends on discovering every meaningful input. Hidden environment or tool-version dependencies can create the same stale-result problem even after headers are included.

  34. Software engineering

    A design is ready when it has a feedback loop.

    If the output can't be checked and fed back to the input, you can't tell whether it works. That loop is my test for 'ready to build'.

    How it works

    The problem

    A design can name its components and still leave nobody able to tell whether the result meets the goal.

    Why it happens

    Without a route from output back to a decision, an observed error changes nothing. The design describes production of an output but not how it learns that the output is wrong.

    How to work through it

    1. State the end goal without choosing tools, then name the observable result that would demonstrate it.
    2. Draw the path from that observation back to the input or decision it can change, including who acts and when the loop stops.

    The solution

    For a generated report, identify the source totals, the comparison with those totals and the correction when they disagree. That loop is meaningful before choosing a report engine.

    How to check it

    Walk one plausible bad output through the drawing. Someone should be able to say how it is detected, what changes next and what result ends the correction.

    Where this stops

    A feedback loop does not make a goal useful or a measurement accurate. Those choices remain design questions, and some checks need a human rather than automatic retries.

  35. Software engineering

    A stub that lies finds different bugs than one that fails.

    Exit non-zero catches ignored errors. Exit zero while doing nothing catches code that treats an exit status as proof of state.

    How it works

    The problem

    A caller can handle an explicit command failure correctly while still accepting a command that reports success without doing its work.

    Why it happens

    An exit status describes what the process reported. It is not an independent observation that a file, service or database reached the requested state.

    How to work through it

    1. Replace the dependency with a stub that exits non-zero and check that the caller surfaces the error.
    2. Use a second stub that exits zero but changes nothing. Ask the caller to perform the same operation and inspect its next decision.

    The solution

    Check the postcondition that matters. For a file writer, require the expected new content before reporting completion, rather than trusting a successful process exit.

    How to check it

    The first stub should trigger failure handling. The lying stub should fail the state check and must not produce a success report.

    Where this stops

    Choose an observable postcondition within the caller’s authority. Eventual consistency may require bounded retries, so an immediate read is not always the right oracle.

  36. Software engineering

    Tests can be complicit in the bug.

    If the test was written from the same wrong reading of the spec, it passes with the code. Check what a test assumes, not just that it's green.

    How it works

    The problem

    A passing test can agree with the implementation and still disagree with the requirement. That makes a green suite a weak answer to a disputed interpretation.

    Why it happens

    The expected result may have been copied from the same assumption that shaped the code. Reusing its helper in the test can make the same fault cancel on both sides.

    How to work through it

    1. Start with the requirement and write one small input whose correct output can be worked out without the implementation.
    2. Compare that result with the test oracle, then deliberately break the disputed rule and see whether the test notices.

    The solution

    Give the test an independent basis for its answer. For a boundary rule, include values just below, at and above the boundary.

    How to check it

    The deliberately wrong implementation should fail for the intended reason. A passing mutation means the test has not established that rule.

    Where this stops

    Independence reduces shared assumptions; it does not prove the requirement itself is right. Keep ambiguous wording open for review.

  37. Software engineering

    A skip that looks deliberate hides forever.

    A test that quietly skips on a missing dependency can stay unrun for the life of the project. Make skips loud, or count them as failures.

    How it works

    The problem

    A green suite can mean that a check passed, or that it never ran. A missing optional dependency can quietly turn a meaningful test into a permanent skip.

    Why it happens

    The harness may interpret an unavailable prerequisite as an expected condition. Unless something records the intended coverage, the same missing dependency can hide a guard on every run.

    How to work through it

    1. List which tests are required for the job and which are genuinely optional. Declare the required dependencies in the environment that runs those tests.
    2. Make the result show the number of executed and skipped checks, with a concrete reason for each skip. Treat a missing required check as a failure.
    3. Remove a required dependency in an isolated run and confirm that the job refuses to claim success. Audit similar skip patterns rather than fixing one line and assuming the class is covered.

    The solution

    Make absence visible at the level that decides whether the work is complete. A skip can be a useful result when it is explicit and does not silently remove promised coverage.

    How to check it

    The normal environment should execute the required checks. The deliberately incomplete environment should produce a clear missing-prerequisite failure, not an indistinguishable green verdict.

    Where this stops

    Some integrations cannot run on every host. Keep those intentional limits documented and visible instead of making every optional environment a hard failure.

  38. Software engineering

    '|| exit 0' only exits its own line.

    Each recipe line in a Makefile runs in its own shell, so an early exit on one line doesn't stop the next.

    How it works

    The problem

    A Make target can decide to skip work on its first line and still run the next line. That is surprising if the recipe is read as one ordinary shell script.

    Why it happens

    By default, Make starts a separate shell for each recipe line. An exit ends the shell running that line; a successful exit tells Make it may continue with the next line.

    How to work through it

    1. Read the recipe as separate shell invocations unless it explicitly uses a single-shell mode. Mark the lines that are supposed to share a condition or variable.
    2. Put the condition and the work it controls in one shell block, or move the procedure into a script with an explicit exit contract.
    3. Exercise both branches. The skip path should show that the protected command never ran, not merely that the target returned success.

    The solution

    Keep the decision inside one shell lifetime. A single recipe line such as `@if test "$(RUN)" = yes; then touch marker; fi` makes the marker conditional. A later independent recipe line would not share that guard.

    How to check it

    Use a harmless marker command in the protected position. On the skip path, expect no marker; on the normal path, expect exactly the intended work and exit status.

    Where this stops

    Make can be configured to run an entire recipe in one shell. Check the actual configuration before applying the default-line model.

  39. Software engineering

    For Python, build means import.

    Compiling won't catch a module importing a name another change removed. The gate has to import every module.

    How it works

    The problem

    A Python file can compile even though importing it fails. A removed function name or a missing dependency may only become visible when the module-level imports actually execute.

    Why it happens

    Bytecode compilation checks syntax. It does not establish that a dependency exists, that an imported name is still exported or that module initialization succeeds in the environment the product uses.

    How to work through it

    1. List the delivered non-test modules and the interpreter and import roots they require. Make that list explicit rather than relying on whichever modules tests happen to touch.
    2. Import the declared modules in a fresh interpreter with the project environment. Capture failures per module so one early exception does not hide the rest.
    3. Keep compilation and runtime tests where they add value, but make a successful import part of the build claim for this kind of Python project.

    The solution

    Define build in terms of the runtime contract. The useful question is whether the delivered module can be loaded where it is meant to run.

    How to check it

    An illustrative regression removes an exported name that another module imports. Syntax checks can stay green, while the import gate must fail and name the affected module.

    Where this stops

    Importing modules can execute code. Run the gate in an isolated test environment, and keep external effects out of module initialization.

  40. Software engineering

    core.fileMode=false drops exec bits silently.

    With file modes ignored, a chmod +x never reaches the commit, and the script fails on the next clone.

    How it works

    The problem

    A script can run locally after chmod +x and still arrive non-executable in a fresh clone.

    Why it happens

    Git records an executable bit in its index. When core.fileMode is false, ordinary working-tree mode changes are ignored, so chmod alone may never update the committed mode.

    How to work through it

    1. Inspect both the local permissions and the mode recorded by Git.
    2. Use a supported index mode update when the filesystem cannot reliably track it.
    3. Review and commit the mode change along with the script.

    The solution

    For example, an executable script should normally appear with mode 100755 in the index rather than 100644. The file’s local ls output cannot establish what another clone will receive.

    How to check it

    Inspect the staged mode change and launch the script from a fresh disposable clone on a compatible filesystem. That checks the committed artifact rather than the author’s workspace.

    Where this stops

    Disabling file-mode detection can be legitimate on some filesystems. The fix is to record the intended index mode deliberately, not to assume every environment must use the same local setting.

  41. Software engineering

    A merge tool can succeed and drop your changes.

    A template update reported success while silently keeping the template's side of conflicted files. Diff the result; don't trust the exit code.

    How it works

    The problem

    A template update can exit successfully while leaving important project changes in reject files instead of the resulting source.

    Why it happens

    With some conflict modes, tool success means the update operation completed, not that every conflict was resolved in favor of the intended project behavior. The template side may remain active.

    How to work through it

    1. Rehearse the update on a disposable copy with a clean recorded baseline.
    2. Inspect the diff, reject files and conflict markers after the command.
    3. Resolve the intended behavior and run the project’s checks before accepting the update.

    The solution

    For example, a local authentication customization moved into a .rej file is no longer active just because the update returned zero. Compare the resulting code and contract, not only the command status.

    How to check it

    Use a fixture with an intentional project/template conflict and confirm the review procedure discovers it. Exercise the preserved project behavior after resolution.

    Where this stops

    Conflict handling varies by tool version and options. Follow the actual migration procedure; restarting an interrupted update incorrectly can skip required migrations even when the files look plausible.

  42. Software engineering

    Lint early, or integrate late.

    Hardware integration is slow, so a mistake found there costs days. Lint and static formal move the check to where it is cheap.

    How it works

    The problem

    A structural RTL mistake found during subsystem integration costs more to isolate because many blocks and assumptions are already interacting.

    Why it happens

    Lint and static analysis can inspect local rules before a long simulation or full integration run. Earlier feedback keeps the investigation close to the change that introduced the problem.

    How to work through it

    1. Choose relevant lint and static-formal goals for the block.
    2. Run them through a repeatable script during local development.
    3. Review violations and govern waivers before accepting the block into the next integration stage.

    The solution

    For example, a width mismatch can be caught at a module interface before it turns into unexpected data several blocks downstream. The earlier report gives a smaller search area than a late behavioral symptom.

    How to check it

    Use a known-bad fixture to confirm the normal flow runs the intended rule. Check tool logs and loaded configuration, not just the wrapper’s exit status.

    Where this stops

    Early analysis complements simulation and later implementation checks. It does not prove that the requirements are right or that every functional path works, and no new estimate of days saved is claimed.

  43. Software engineering

    Between two equal designs, pick the one easier to draw.

    The design that makes a cleaner block diagram is the one you will still understand in a year.

    How it works

    The problem

    Two designs can solve the same immediate task while leaving very different amounts of explanation for the next maintainer.

    Why it happens

    A clean block diagram often exposes clear responsibilities and a small number of meaningful interfaces. A design that needs many crossing arrows may be spreading state or policy across too many owners.

    How to work through it

    1. Draw both candidate designs at the same level of detail.
    2. Label the data, ownership and failure behavior at each boundary.
    3. Prefer the simpler structure when it meets the real requirements and keeps changes local.

    The solution

    For example, a validation stage before storage is easier to reason about than several storage callers each applying a different part of the same rule. The diagram makes the duplicated ownership visible.

    How to check it

    Walk a normal request, a failure and a likely future change through the chosen diagram. Check whether a reader can locate the responsible module in the code.

    Where this stops

    A tidy picture can hide complexity by omitting details. Treat ease of drawing as a design test, not proof that the system is correct or that the fewest boxes always win.

  44. Software engineering

    Rich will happily colour your dollar signs.

    Its number highlighter split '$0.04' with ANSI codes. Turn highlighting off for text that has to stay intact.

    How it works

    The problem

    A terminal footer can display a price correctly to a person while breaking code that expects the amount to be one intact string.

    Why it happens

    Rich’s automatic highlighter can insert ANSI formatting around numeric portions of text. Those invisible escape sequences divide values such as $0.04 in the raw output.

    How to work through it

    1. Decide whether the output is for display, machine parsing or both.
    2. Disable automatic highlighting for text whose byte sequence must stay intact.
    3. Provide a separate plain or structured output mode for machine consumers.

    The solution

    For example, a cost footer can use highlight=False while retaining deliberate styling around the whole line. That prevents a number-specific highlighter from modifying the middle of the amount.

    How to check it

    Capture raw output with the actual terminal settings and inspect the complete amount. Test colored and noncolored paths, because a nonterminal capture may omit ANSI sequences and miss the bug.

    Where this stops

    Other styling can still add escapes. Turning off highlighting is a targeted fix, while a supported JSON or plain-text interface gives downstream parsers a clearer contract.

  45. Software engineering

    Traceability is arithmetic, not testimony.

    A tracker status is someone's claim. A trace computed from the repo and test markers can be checked, and fails on any requirement no test names.

    How it works

    The problem

    A tracker can say that every requirement is tested while the repository contains no check for one of them.

    Why it happens

    A manually edited status is testimony. A generated trace can compare the actual requirement inventory with test markers and report the exact missing or undefined links.

    How to work through it

    1. Give requirements stable identifiers and identify their acceptance criteria.
    2. Mark the test functions that check those criteria.
    3. Generate the matrix from the current repository and fail on missing or unknown links.

    The solution

    For example, a requirement with three numbered criteria needs evidence for those criteria, not merely one marker naming its parent. Function-level markers also stop a removed test from remaining apparently covered by a module-wide label.

    How to check it

    Delete or rename a marked fixture test and confirm the trace becomes incomplete. Check that an empty requirement inventory cannot silently produce a perfect result.

    Where this stops

    A marker proves a claimed link exists; it does not prove the test is relevant or correct. Review the assertion behind the marker and keep the generated matrix tied to the exact revision.

  46. Software engineering

    Never name a Makefile variable AS.

    AS, CC and CXX are GNU Make built-ins and are never empty, so '--as $(AS)' quietly became '--as as'.

    How it works

    The problem

    A Make recipe can pass an unintended identity argument even when no developer explicitly configured that variable.

    Why it happens

    GNU Make supplies built-in variables. AS commonly names the assembler, so a recipe using $(AS) as a convenient “act as” parameter can receive as instead of an empty value.

    How to work through it

    1. Choose a task-specific variable name such as WHO for the requested identity.
    2. Inspect the variable’s origin and the expanded recipe while debugging.
    3. Validate the supplied identity before invoking the downstream command.

    The solution

    For example, --as $(AS) can become --as as. Renaming the configuration avoids a collision with Make’s toolchain namespace and makes the argument’s meaning clearer to callers.

    How to check it

    Use a dry-run recipe inspection with no override, then with an explicit WHO value. Confirm the default behavior and the expanded argument list before exercising a real privileged operation.

    Where this stops

    Built-in variables vary with Make settings and platforms, and environment overrides can add more collisions. Namespacing avoids the immediate trap; validation is still needed at the command boundary.

  47. Software engineering

    Unverified is not refuted.

    When a checker dies before finishing, its findings are a third state. Scoring them as false quietly drops real bugs.

    How it works

    The problem

    A review result can become misleading when a checker that never finished is scored as though it disproved the finding.

    Why it happens

    There are two questions: did the check complete, and what did it conclude? A crash, timeout or missing dependency answers neither true nor false about the proposed bug.

    How to work through it

    1. Record execution status separately from the finding verdict.
    2. Use an explicit unverified state for interrupted or incomplete checks.
    3. Retry or investigate that state before treating the finding as resolved.

    The solution

    For example, a reproducer that times out should retain its evidence and timeout reason. Calling it a false positive would erase a potentially real issue merely because the checking path failed.

    How to check it

    Feed the review pipeline one confirmed issue, one refuted issue and one deliberately interrupted check. Confirm that all three remain distinct in the report and any aggregate score.

    Where this stops

    Unverified does not mean the finding is probably correct. It means the evidence is unfinished. A later completed check may support or refute it, and the report should preserve that transition.

  48. Software engineering

    A hardcoded value is a question nobody asked.

    It means no one thought about what might change. Put it in config, and let the code and the docs both read it from there.

    How it works

    The problem

    A value copied into code and documentation can require a release and several synchronized edits whenever the operating environment changes.

    Why it happens

    The same policy now has multiple owners. One copy can change while another stays plausible, leaving behavior and instructions in disagreement.

    How to work through it

    1. Identify values expected to vary across deployments or over time.
    2. Give each value one configuration owner with validation and a documented default.
    3. Have code and generated documentation read that owner rather than copying the value.

    The solution

    For example, a retry limit that differs by environment belongs in validated settings. A mathematical constant inside an algorithm usually does not. The distinction is whether the value represents a changeable decision.

    How to check it

    Change the setting in a fixture and confirm the behavior and displayed documentation agree. Reject invalid values at startup instead of accepting a configuration that cannot work.

    Where this stops

    Configuration adds another interface to maintain. Keep true invariants in code, avoid exposing internal details without a use case, and record which changes require a restart.

  49. Software engineering

    Mock the world, then check the mock once.

    Most tests should be fast and mocked. A few unmocked tests on storage, auth and timing prove the mocks aren't lying.

    How it works

    The problem

    A test suite can be fast and green while depending on a fake version of storage, authentication or timing that the real system does not provide.

    Why it happens

    Mocks verify behavior under stated assumptions. If those assumptions drift from the real dependency, the test can keep confirming an interface that no longer exists.

    How to work through it

    1. Use mocks for most local logic and contract cases.
    2. Choose a few critical paths where a wrong assumption would be costly.
    3. Exercise those paths against the real dependency through the same interface production uses.

    The solution

    For example, a mocked database can confirm that save is called. An unmocked check should save, reconnect and read the data back, because persistence across a connection boundary is the property at risk.

    How to check it

    Compare the observed real behavior with the mock’s contract. Include authentication refusal or a relevant timing boundary rather than testing only a happy path.

    Where this stops

    The right balance depends on risk and test cost; a fixed mock percentage is not a universal rule. Real-dependency tests also need isolation and repeatable cleanup to remain useful.

  50. Software engineering

    Tight coupling is the real sin.

    A wrong choice is fine if the module can be swapped out. Coupling is what turns a wrong choice into a rewrite.

    How it works

    The problem

    A poor component choice becomes expensive when its assumptions spread through the rest of the system.

    Why it happens

    Coupling means other modules depend on details that should belong to one component. Replacing that component then requires changing callers, stored data or control flow across the system.

    How to work through it

    1. Define the behavior callers need before choosing the implementation.
    2. Keep vendor-specific types and settings behind a small adapter.
    3. Make dependencies flow through the contract rather than importing implementation details everywhere.

    The solution

    For example, a storage interface can expose read and write operations without requiring every caller to know a database driver’s connection object. Another backend then has one boundary to implement.

    How to check it

    Try a second minimal implementation in a fixture. If replacing it requires edits across the core pipeline, inspect which assumptions escaped the boundary.

    Where this stops

    Indirection has a cost, and an interface cannot predict every future need. Keep it tied to real behavior instead of creating speculative layers. Swappability reduces the cost of a wrong choice; it does not make every choice equally good.

  51. Software engineering

    Incremental rebuilds matter most when reruns cost money.

    Skipping unchanged steps saves minutes for a compiler and real money for an LLM step, where every rerun is billed.

    How it works

    The problem

    Rerunning an unchanged language-model stage spends money again even when its previous accepted result is still applicable.

    Why it happens

    A dependency graph makes it possible to identify which results depend on changed inputs. Without that relationship, a small edit can trigger a full rebuild or leave old results looking current.

    How to work through it

    1. Record each stage’s inputs, recipe identity and accepted output.
    2. Invalidate affected descendants when a relevant input changes.
    3. Reuse unaffected accepted results and rerun stale stages in dependency order.

    The solution

    For example, editing one source document should invalidate its summary and consumers, while leaving unrelated summaries available. The reuse decision belongs to deterministic input tracking, not to a model’s opinion that nothing important changed.

    How to check it

    Change one fixture input and inspect the exact set of stages marked stale. Confirm that unrelated stages remain reusable and that changed configuration also invalidates the right results.

    Where this stops

    The declared dependency set must be complete. A reused model answer remains a saved accepted answer, not a promise that generating it again would produce identical text.

  52. Software engineering

    Regex alternation takes the first match, not the longest.

    oa|oasis matched '.oa' and truncated the file extension. Put longer options first or add a boundary lookahead.

    How it works

    The problem

    A filename extractor can return a plausible shorter extension instead of the full extension present in the text.

    Why it happens

    In a typical backtracking regular expression, alternatives are tried in order. If oa matches the start of oasis and no following boundary is required, the first successful branch can end the match early.

    How to work through it

    1. Put longer overlapping alternatives first and require the intended end boundary.
    2. Add fixtures for every overlapping extension and for suffix text that should not match.
    3. Inspect downstream stored results as well as the parser after fixing the expression.

    The solution

    For example, matching .oasis must not emit .oa. A boundary or full-token check protects the intended token more clearly than relying on branch order alone.

    How to check it

    Compare extracted full paths against expected paths, then check any derived records that the old extractor populated. A corrected parser does not automatically repair rows already saved.

    Where this stops

    Regex is often too weak for a complete command language. Prefer a parser, tool-reported inputs or observed file access when the surrounding syntax matters.

  53. Software engineering

    Random checks need a seed, a snapshot and an oracle.

    The seed replays any failure, one snapshot keeps a race from looking like a bug, and a simple evaluator that shares no code says what's right.

    How it works

    The problem

    A randomized live-data check can report a mismatch that cannot be reproduced, leaving it unclear whether the system or the check was wrong.

    Why it happens

    Three independent sources of uncertainty get mixed together: generated input, changing data and a checker that may share the system’s own bug.

    How to work through it

    1. Record a seed and the exact generated query.
    2. Run the system and the reference evaluator against the same frozen data snapshot.
    3. Use a deliberately simple evaluator that shares neither the parser nor query builder.

    The solution

    For an illustrative filter check, compare exact row-id sets, not only counts. Equal counts can hide one missing row and one extra row. Save the snapshot identity and mismatch so a later investigation starts with the same problem.

    How to check it

    Replay a saved seed and snapshot and confirm the same query and result sets. Plant one known filtering defect to prove that the independent check detects it.

    Where this stops

    The cited design is a proposal, not evidence that a periodic production checker was installed. A simple oracle can still be wrong, so give it its own examples and boundaries.

  54. Software engineering

    FastAPI uses the first route that matches.

    A duplicate path silently shadows the later handler. Nothing warns you, so test that each route reaches its own code.

  55. Software engineering

    A process-wide signal can land on any thread.

    os.kill targets the whole process, so the kernel may deliver it to any thread that hasn't blocked it. signal.pthread_kill aims at the thread you mean.

    How it works

    The problem

    A signal test can disturb the wrong thread when it assumes that targeting a process also selects a particular worker.

    Why it happens

    A process-directed signal may be delivered to any eligible thread that has not blocked it. Thread-directed delivery selects a thread, but Python’s signal-handler rules remain a separate constraint.

    How to work through it

    1. Write down whether the test needs process delivery or delivery to a specific thread.
    2. Control signal masks explicitly and restore them during cleanup.
    3. Use thread-directed delivery only with a clear understanding of the runtime’s handling rules.

    The solution

    For example, a test of an interrupted worker should identify that worker rather than relying on whichever thread the kernel chooses. Keep the signal action small and arrange deterministic synchronization around the expected interruption.

    How to check it

    Repeat the isolated test and check which thread was targeted and which handler behavior occurred. A test that merely observed some signal is too weak.

    Where this stops

    Python handlers normally run in the main thread, and pthread_kill is not a general way to execute Python callbacks in a worker. The source trail for this exact historical assertion is weak; treat the procedure as general guidance.

  56. Software engineering

    Hashing makes inputs reproducible, not outputs.

    Content addressing proves you fed the same things in. Outputs repeat only if the tool is deterministic, and LLM steps often aren't.

    How it works

    The problem

    An input hash can be used to make a stronger reproducibility claim than it actually supports.

    Why it happens

    A digest identifies bytes supplied to a run. It says nothing by itself about random sampling, parallel execution, remote services or hidden inputs that can change the output.

    How to work through it

    1. Record input identities and the execution environment separately.
    2. State whether a step promises repeatable bytes, equivalent meaning or only acceptable quality.
    3. Keep the produced output and its own digest alongside the run evidence.

    The solution

    For an illustrative language-model step, store the prompt, model identifier and generation settings, then preserve the actual answer. Repeating the same prompt may produce another valid answer; that does not mean either input digest was wrong.

    How to check it

    Repeat a representative run and compare outputs against the promised level of agreement. If they differ, identify the source of variation instead of calling the hash a failed determinism guarantee.

    Where this stops

    Even deterministic code needs all relevant inputs declared. A matching hash is evidence of matching captured inputs, not proof that the capture was complete or the result correct.

  57. Software engineering

    Type hints are port declarations.

    In SystemVerilog the ports say what goes in and out before anything runs. Type hints do the same for a Python function, and a checker can read them.

    How it works

    The problem

    A function’s calling convention is harder to review when readers must inspect its body to discover expected inputs and outputs.

    Why it happens

    Type hints expose part of the interface before execution. Like declared port types, they let tools check whether connected pieces agree on the values they exchange.

    How to work through it

    1. Annotate the public inputs and return value with useful domain types.
    2. Run a type checker as a separate verification step.
    3. Validate data arriving from untrusted or untyped boundaries at runtime.

    The solution

    For example, an annotated parser returning a result object is easier to use correctly than one returning an undocumented mixture of strings and dictionaries. The annotation helps callers, while runtime validation still handles malformed input.

    How to check it

    Change a fixture caller to pass an incompatible type and confirm that the configured checker reports it. Then exercise a malformed external value to check the runtime boundary.

    Where this stops

    Python annotations do not automatically enforce types when code runs. Broad Any annotations and unchecked dependencies weaken the guarantee, and type correctness does not establish the function’s intended behavior.

  58. Software engineering

    If you can't draw it, you don't understand it.

    A system should fit on a block diagram, and the code should be laid out like the diagram, so a reader knows where to look without grep.

    How it works

    The problem

    A reader should not need a repository-wide search to discover which part of a system owns a task.

    Why it happens

    When code is arranged by incidental helpers rather than system responsibilities, the architecture exists only in the author’s head. Changes then require rediscovering the same boundaries.

    How to work through it

    1. Draw the main responsibilities and the data crossing their boundaries.
    2. Give each block a clear owner in the code layout.
    3. Walk one real request from entry point to result and check that the diagram follows it.

    The solution

    For example, a pipeline with ingest, validate and export blocks should make those three responsibilities easy to locate. Shared utilities can support the blocks, but should not hide the direction of data flow or become an unexplained extra core.

    How to check it

    Ask a fresh reader where a validation change belongs, then compare the answer with the diagram and actual imports. Disagreement reveals a boundary or naming problem.

    Where this stops

    A diagram is a model, so small helpers need not each become a box. Keep the view at a useful level and revise it when responsibilities move.

  59. Software engineering

    `import a.b` binds a, not b.

    The name added to your namespace is the top package. Any tool that resolves or patches imports has to follow that rule.

    How it works

    The problem

    A code graph or patch target can point at the wrong object even when the import statement itself is valid.

    Why it happens

    Python separates loading a module from binding a local name. With import a.b, the local name is a; treating a.b as the bound name loses the first step of attribute lookup.

    How to work through it

    1. Parse the import form and any alias before resolving a later call.
    2. Track the local binding separately from the fully qualified module path.
    3. Cover plain imports, aliases and from-imports with small examples.

    The solution

    In an illustrative module, import package.worker makes package.worker.run() reachable through package. By contrast, import package.worker as worker binds worker. A resolver needs both the spelling used locally and the target it denotes.

    How to check it

    Inspect the module namespace in a small fixture and compare the graph’s resolved target with Python’s actual lookup. Include a case where a package and a function share a name.

    Where this stops

    Dynamic imports, re-exports and attribute chains need additional rules. Mark unresolved targets rather than turning a partial static analysis into a confident but false edge.

  60. Software engineering

    Same commit, different report means a hidden input.

    Reruns with no code change still differed, because released IP, flow scripts and tool versions weren't declared. Declare them before trusting a cache.

    How it works

    The problem

    A cache keyed only by source commit can reuse a result produced under a different external environment.

    Why it happens

    A flow can consume released IP, scripts, libraries and tool builds that live outside the source repository. The commit stays fixed while these inputs change, so apparently identical reruns are not identical experiments.

    How to work through it

    1. Inventory external inputs at each stage, including tool builds and library releases.
    2. Record immutable versions or content digests in a run manifest.
    3. Build the cache key from the complete declared input set.

    The solution

    Compare report content separately from run metadata. An illustrative report with a new build number is not a functional change; a changed timing result under an unchanged source commit is a reason to inspect the environment.

    How to check it

    Rerun a small stage with pinned inputs, then deliberately change one external input and confirm that the cache misses. Compare normalized reports only where removing metadata is justified.

    Where this stops

    Declaring inputs is necessary for safe caching, but does not make a nondeterministic tool deterministic. The historical source reports hidden-input drift; this draft does not reproduce that trial.

  61. Software engineering

    A design simulator isn't an end-to-end test.

    Load-testing a diagram shows what saturates first. It never runs your code, so it can't find a wrong answer.

    How it works

    The problem

    A design simulation can show a convincing capacity curve while saying nothing about whether the implementation returns the correct result.

    Why it happens

    A model executes assumptions about the system. It does not execute the application code, its database integration or its actual error handling.

    How to work through it

    1. Use the design model to explore queues, contention and expected bottlenecks.
    2. Build separate tests that run the real application with real local dependencies.
    3. Add controlled delay or failure injection where the implementation’s behavior needs checking.

    The solution

    A simulated database queue can reveal which service would saturate first under the chosen assumptions. A real database test asks whether the query and retry logic work when a connection is delayed or dropped.

    How to check it

    Label the result by what ran. Inspect assumptions for the simulation and actual response correctness for the implementation test; a load curve alone does not establish either one.

    Where this stops

    The source is a research comparison, not a recorded production validation. Real-code tests also cover only the workloads and faults exercised, so neither approach replaces the other.

  62. Software engineering

    Instrument the harness before blaming the tool.

    A 'my change does nothing' result was the test harness cloning the wrong tree. Harness faults look exactly like tool limits.

    How it works

    The problem

    A template change can appear to have no effect because the test harness never included the edited files.

    Why it happens

    A fixture that clones committed content can omit uncommitted changes. The test then exercises an older tree while its author thinks it is testing the current one.

    How to work through it

    1. Record the source tree or revision used by the harness.
    2. Inspect the relevant file inside the actual fixture.
    3. Repair the fixture input before investigating the tool’s behavior.

    The solution

    If a migration change is absent from the cloned fixture, rerunning the migration test only repeats the wrong experiment. Confirm that the expected bytes reached the fixture before concluding that the template tool ignores them.

    How to check it

    Add a temporary observable marker in a disposable test input and check its presence at the tool boundary. Then remove the marker and rerun the real behavior check on the corrected fixture.

    Where this stops

    Instrumenting the harness establishes what was tested, not that the tool is correct. Once the right input is confirmed, continue investigating any remaining tool or configuration issue.

  63. Software engineering

    Use asymmetric fixtures, or swapped fields pass.

    If test values collapse to equal numbers, a bug that swaps two fields is invisible. Use distinct values like 33.3 and 50.0.

    How it works

    The problem

    A test can compare the right fields and still fail to detect that the implementation swapped them when their fixture values are equal.

    Why it happens

    Equal inputs make the correct and faulty mappings observationally identical. The test has no signal that distinguishes the two behaviors.

    How to work through it

    1. Identify output fields that could be confused or swapped.
    2. Give their fixture inputs distinct values that survive rounding and transformation.
    3. Check each output against its own independently derived expectation.

    The solution

    For two percentages, use values such as 33.3 and 50.0 rather than two paths that both produce 50.0. A swapped mapping then gives visibly wrong output instead of passing by coincidence.

    How to check it

    Deliberately swap the two fields in a disposable mutation and expect the test to fail. If it still passes, inspect whether formatting or shared helper logic erased the distinction.

    Where this stops

    Distinct values do not guarantee independence. Keep the expected calculation separate from the implementation, and include boundary cases as well as the asymmetric example.

  64. Software engineering

    A gate you've never seen fail is unproven.

    Several gates passed over real damage before anyone checked they could go red. Inject the fault, watch it fail, and record what you injected.

    How it works

    The problem

    A gate can stay green over real damage if its test path never exercises the condition it claims to reject.

    Why it happens

    A passing normal case shows that valid work is accepted. It does not show that the detector or final failure signal works in the opposite direction.

    How to work through it

    1. Choose a representative fault the gate is required to catch.
    2. Inject it into an isolated fixture and run the gate.
    3. Record the fault, expected rejection and actual result, then remove the fault and check recovery.

    The solution

    If a gate promises to reject a missing required field, delete that field in a disposable input. The gate should fail because of that omission, not because the fixture is broken in some unrelated way.

    How to check it

    Inspect both the local diagnostic and the final caller-visible status. A message saying “failed” is insufficient if the overall command still returns success.

    Where this stops

    One planted fault proves only that path. Select faults for distinct failure classes, and keep destructive injections away from live data and systems.

  65. Software engineering

    Some checks need no oracle at all.

    A AND B must sit inside A. count(A) plus count(NOT A) must equal the total. Relations like these catch parser bugs cheaply.

    How it works

    The problem

    A query layer can return plausible rows without a known exact answer for every possible query. That makes broad parser checks difficult.

    Why it happens

    Some results must obey relationships even without a separate expected-answer system. These checks are often called metamorphic checks.

    How to work through it

    1. Choose a fixed data snapshot and a relation that should hold.
    2. Run the related queries against that same snapshot.
    3. Compare row sets as well as counts, and save the inputs needed to replay a failure.

    The solution

    For example, rows matching A AND B must be contained in the rows matching A. On a complete two-valued partition, count(A) plus count(NOT A) must equal the total. A broken parser can violate either relation.

    How to check it

    Deliberately alter a query interpretation in a safe test and expect a relation check to fail. A repeatable seed and snapshot make the failure useful to investigate.

    Where this stops

    NULL semantics, pagination and changing data can invalidate a relation’s assumptions. These checks complement independent expected answers; an implementation can preserve the chosen relations and still be wrong elsewhere.

  66. Software engineering

    Git happily stores Foo.md and foo.md.

    Linux sees two files; a case-insensitive checkout sees a collision. Check case-folded uniqueness wherever filenames are generated.

    How it works

    The problem

    A repository that works on Linux can fail to check out cleanly on a case-insensitive filesystem when two filenames differ only by case.

    Why it happens

    Git records path names distinctly, while the target filesystem may treat those paths as the same file. One working tree cannot represent both names reliably.

    How to work through it

    1. Normalize generated names according to one naming policy.
    2. Check case-folded uniqueness before writing or accepting paths.
    3. Reject a collision with both conflicting names so it can be resolved deliberately.

    The solution

    Foo.md and foo.md are different Git entries but can collide on a default Windows checkout. Choosing one canonical spelling before generation avoids leaving the conflict for the next machine to discover.

    How to check it

    Run the collision check over the full generated path set, including existing tracked files. Expect the example pair to fail even when the check runs on a case-sensitive host.

    Where this stops

    Case folding is one portability risk. Unicode normalization, reserved names and path-length limits can also matter; a case-only check does not establish complete cross-platform compatibility.

  67. Software engineering

    Git only stores the exec bit.

    Group, setgid and other mode intent is lost on clone, so it has to live in a package or install script.

    How it works

    The problem

    A fresh clone can contain the right files while losing the ownership and permission layout needed for a shared installation.

    Why it happens

    Git tracks a file’s executable distinction, not the full Unix mode, owner or group. Group-write and setgid intent therefore cannot live only in the working tree’s current permissions.

    How to work through it

    1. Keep content and executable intent in Git.
    2. Express ownership and additional modes in the package or installation procedure.
    3. Reapply and validate those settings when the deployment changes.

    The solution

    A shared directory may need a group owner and setgid so new files inherit the group. Record that rule in setup rather than assuming a clone will preserve the source directory’s mode.

    How to check it

    Inspect a clean installation as the intended service and team identities. Check effective access and group inheritance, not just whether the tracked scripts retained their executable bit.

    Where this stops

    Hooks can help restore local state but are not automatically installed by every clone. The deployment process must own their installation or enforce the permissions through another reliable mechanism.

  68. Software engineering

    Prove the parallel run matches the serial one.

    Run the suite serially, with four and sixteen workers, and shuffled. Hidden order dependence only shows when the order changes.

    How it works

    The problem

    A suite that passes in one parallel run may still depend on test order or shared state. Speed alone does not establish that parallel execution is equivalent.

    Why it happens

    Workers change execution order and overlap. Tests that share files, environment or mutable fixtures can interfere in ways a serial run hides.

    How to work through it

    1. Run the same selected suite serially.
    2. Compare runs with four and sixteen workers.
    3. Add a shuffled-order run and investigate differences in both outcomes and collected cases.

    The solution

    Treat a parallel-only failure as evidence about isolation. If one test changes a shared setting and another assumes its original value, a scheduling change can expose a real weakness in the suite.

    How to check it

    Compare collected, passed, failed and skipped cases, not just the final exit status. Expect the same intended coverage and outcomes across the selected execution modes.

    Where this stops

    These runs sample possible schedules; they do not prove every interleaving safe. Use isolated fixtures and separate checks for truly concurrent behavior, and run heavy suites on appropriate compute resources.

  69. Software engineering

    Climb the test ladder: mock, fake, real-local, real.

    Each rung catches what the one below can't. A green mock says the logic holds; only the top rung says the world agrees.

    How it works

    The problem

    A green mock-based suite can confirm the intended logic while missing a wrong assumption about the external system.

    Why it happens

    A mock behaves as its author says it should. It cannot reveal a disagreement between that assumption and a real API, filesystem, identity or timing boundary.

    How to work through it

    1. Use mocks for fast checks of logic and contracts.
    2. Add a fake with meaningful state, then real components in a local setup.
    3. Use a small live smoke check for the critical external assumptions that lower rungs cannot establish.

    The solution

    For persistence, a mock can confirm that a write was requested. A real store check asks whether the data survives and is readable through the actual integration. Those are different claims and need different evidence.

    How to check it

    Record which rung ran and what it established. Confirm that the live check really reached the intended system rather than silently falling back to a mock.

    Where this stops

    Live checks need controlled inputs and appropriate authorization. More rungs do not justify unsafe production tests, and identical green results are useful only if each rung checks something distinct.

  70. Software engineering

    Don't assert on message text.

    Hundreds of tests checked error wording, so every copy edit broke them. Assert on a code or a field instead.

    How it works

    The problem

    A harmless wording change can break many tests when they assert on an error sentence instead of the behavior that produced it.

    Why it happens

    The tests bind correctness to presentation. The message may change while the error category, state and exit behavior stay correct.

    How to work through it

    1. Give the result a stable code or structured field.
    2. Create a fixture that isolates the condition being checked.
    3. Assert on that structure and reserve text assertions for wording that is itself part of the contract.

    The solution

    For example, an invalid range can return an INVALID_RANGE code plus a helpful sentence. The behavior test should check the code and rejected operation, so improving the sentence does not rewrite the suite.

    How to check it

    Change only the human wording and expect behavior tests to stay green. Then change the error code or allow the invalid operation and expect a relevant test to fail.

    Where this stops

    User-facing copy can deserve dedicated checks, and some command-line interfaces promise exact text. Keep those expectations explicit rather than using message matching as a substitute for a missing structured result.

  71. Software engineering

    Delta debugging finds a minimal set, not the smallest.

    ddmin guarantees that dropping any one difference stops the failure, not that no smaller set exists. Both ends must reproduce before you start.

    How it works

    The problem

    A reduced failure case can be called “the smallest” even though the reduction algorithm has proved something narrower.

    Why it happens

    ddmin finds a set that is 1-minimal: removing any one remaining difference loses the failure. That does not rule out a different, smaller failing set.

    How to work through it

    1. Reproduce the passing baseline and the failing full-change case first.
    2. Reduce differences on an isolated copy while preserving the same failure test.
    3. After reduction, test removal of each remaining difference and report the guarantee accurately.

    The solution

    If change a fails alone while b and c fail only together, a search can return {b, c}. Neither member can be removed from that result, even though {a} is a smaller failure case.

    How to check it

    Expect the reduced set to reproduce and every single-member removal to stop reproducing. If either original endpoint no longer behaves as recorded, stop and repair the experiment first.

    Where this stops

    Flaky tests and unresolved intermediate outcomes weaken the result. A 1-minimal set is useful evidence about a reproducer, not proof of the unique root cause or globally smallest explanation.

  72. Software engineering

    Finishing first is not happening-before.

    A write that completed earlier is still not visible to a reader unless the two sync through an acquire-release pair. Clock order is not memory order.

    How it works

    The problem

    A reader can run after a writer in wall-clock time and still have no valid synchronization relationship with that write.

    Why it happens

    Happens-before is an ordering defined by the memory model, not by which operation appeared to finish first. Threads need an appropriate synchronization path to publish and acquire shared state.

    How to work through it

    1. Identify the shared data and the flag or event meant to publish it.
    2. Use the required release operation after the write and matching acquire before the read.
    3. Check the whole path, including any transitive synchronization through other threads.

    The solution

    For example, write a payload, publish readiness with release semantics, and have the reader acquire that readiness before using the payload. A delay or an observed completion time does not replace that relationship.

    How to check it

    Trace the actual synchronization edge between the conflicting accesses. A run that happens to return the expected value only shows one execution; it does not establish the memory-order guarantee.

    Where this stops

    The exact operations and scopes depend on the language and hardware memory model. Use its specified primitives rather than assuming every flag or barrier has the needed semantics.

  73. Software engineering

    Fixtures shouldn't share names with real data.

    A find-and-replace over real names rewrote the fixtures too, and tests failed for no real reason.

    How it works

    The problem

    A template substitution can alter the test fixtures as well as the production examples. The generated project then fails tests for an accidental naming collision.

    Why it happens

    A broad replacement does not understand which occurrence is sample data and which is a test input. Shared names make both look eligible.

    How to work through it

    1. Give fixtures clearly synthetic names that do not overlap the real substitution values.
    2. Limit replacement to the intended files and fields.
    3. Verify the generated project, including its unchanged fixture assumptions.

    The solution

    For example, keep a fixture’s fake account name separate from the account token that the template fills. Changing the deployment account should then leave the test’s deliberately invalid account untouched.

    How to check it

    Render with replacement values that resemble realistic names and inspect the fixture diff. Expect only declared template fields to change, then run the generated tree’s own tests.

    Where this stops

    Names reduce accidental overlap but do not replace a proper template boundary. If replacement can still edit arbitrary text, another collision can appear later.

  74. Software engineering

    Write the tests from the plan, before the code.

    Tests written after the code tend to describe the code. Written from the plan, they describe the intent and can't be bent to fit.

    How it works

    The problem

    Tests written after implementation can confirm the shape of the code while missing the behavior the plan actually promised.

    Why it happens

    The implementation becomes the test author’s reference. Mistakes in the code can then be copied into the expected result.

    How to work through it

    1. Write the intended behavior and boundary cases in the plan.
    2. Derive the test cases before implementation begins.
    3. Implement against those cases, then add targeted checks for new risks discovered during the work.

    The solution

    For a filter, the plan should say how empty input, invalid ranges and boundary values behave. Tests can encode those decisions before the parser’s current behavior has a chance to become the accidental specification.

    How to check it

    Compare the tests back to the plan and introduce a fault that violates one promised behavior. Expect the corresponding test to fail for the intended reason.

    Where this stops

    A plan can also be wrong or incomplete. Independent review and integration checks still matter; writing tests first separates two artifacts, but does not make their shared assumptions true.

  75. Software engineering

    A check has three outcomes: yes, no, and couldn't run.

    Callers must tell 'the answer is no' from 'I couldn't ask'. One exit code for both hides a broken check.

    How it works

    The problem

    A caller cannot act correctly if a negative answer and a failed attempt to obtain an answer look identical.

    Why it happens

    One exit status collapses two different states: the check completed and found no match, or the check could not complete at all.

    How to work through it

    1. Document separate results for yes, no and unable to check.
    2. Return those results from every execution path.
    3. Make callers branch explicitly on all three outcomes.

    The solution

    For example, “the file is absent” can support a normal decision, while “permission denied while reading the directory” should stop that decision. Both may look like no result unless the check preserves its error state.

    How to check it

    Exercise a positive fixture, a valid negative fixture and an unavailable prerequisite. Expect three distinguishable outcomes and confirm that the caller does not turn the last one into an ordinary no.

    Where this stops

    The numeric exit codes are an interface choice and need documentation. A nonzero code is not automatically an error if the tool contract uses it for a valid negative result.

  76. Software engineering

    A new feature shouldn't touch the spine.

    If adding a feature means changing the core pipeline, the integration point is wrong. Fix the seam first.

    How it works

    The problem

    Adding a feature becomes expensive when it requires changes throughout the central pipeline. Each new branch increases the amount of core behavior that must be understood again.

    Why it happens

    The extension point does not own a clear contract, so feature-specific details leak into orchestration.

    How to work through it

    1. Locate the part of the core that knows the new feature’s details.
    2. Define the inputs, outputs and failure behavior at that boundary.
    3. Move feature-specific behavior behind the boundary and let the core call the shared contract.

    The solution

    For example, a new model provider should implement the provider interface and register itself. The pipeline should not need a new provider-name conditional at every stage.

    How to check it

    Compare the next feature addition against the boundary. It should be possible to explain its integration through one contract and test that contract without rebuilding the whole pipeline.

    Where this stops

    Some features change the system’s actual responsibilities. In that case the core contract may need a deliberate redesign. The warning is repeated feature-specific edits, not a rule that the core can never change.

  77. Software engineering

    esbuild strips types without checking them.

    A green bundle says nothing about type errors. Run `tsc --noEmit` as its own gate.

    How it works

    The problem

    A TypeScript application can bundle successfully while containing type errors. A green build can therefore be mistaken for a stronger check than it actually provides.

    Why it happens

    esbuild removes type annotations and produces JavaScript. It does not check whether assignments and function calls satisfy the TypeScript types.

    How to work through it

    1. Keep esbuild as the bundling step.
    2. Add tsc --noEmit as a separate required check.
    3. Make the verification command and CI run both steps, with missing tools treated as incomplete verification.

    The solution

    If a function expects a number and a caller passes text, bundling may still work. The type-checking step should catch the mismatch before the bundle is accepted.

    How to check it

    Introduce a small deliberate type mismatch in a disposable copy. Expect the type gate to fail even if bundling succeeds, then remove the fault and expect both checks to pass.

    Where this stops

    Type checks do not establish runtime API correctness. Handwritten interfaces can drift from real responses, so important boundaries also need contract or integration checks.

  78. Software engineering

    Race sanitizers sample, so they can miss and misfire.

    Tracking every reader's clock costs too much memory, so only a sample is kept. Races with old reads slip by, and a wrapped clock buffer reports races that never happened.

    How it works

    The problem

    GSan, a race sanitizer for Triton and Gluon GPU code, can miss a real race or report one that did not occur. Treating its output as a complete account of memory ordering is too strong.

    Why it happens

    GSan uses logical clocks: records of which accesses are ordered by synchronization. Tracking every reader’s clock is expensive. Sampling leaves some older reads untracked, while reuse of a wrapped clock buffer can confuse the recorded history.

    How to work through it

    1. Understand what the sanitizer records and what it samples.
    2. For a report, inspect the conflicting accesses and the synchronization between them.
    3. For a clean run, keep the sampling limit in mind and check critical shared-memory protocols separately.

    The solution

    Inspect synchronization as well as the report. For example, a producer writes a shared value, then signals completion; a consumer waits for that signal before reading it. If that synchronization orders the write before the read, those two accesses are ordered. Without the wait, their apparent order in a log does not establish safety. This is an illustrative check, not a tested kernel.

    How to check it

    For a suspected access pair, identify the synchronization operation that orders one before the other. If supported by the GSan configuration, more read samples or a larger clock buffer can reduce these failure modes at the cost of memory. Repeated silence still does not prove every access is ordered.

    Where this stops

    These limits describe GSan’s sampled clock design. Its synchronization must be instrumented and recognized; code or synchronization outside its coverage can cause missed races or misleading reports. Other sanitizers may track ordering differently.

  79. Software engineering

    A pass over zero items is a failure.

    A live eval where every case was 'not run' scored a pass. A check that selected nothing has proven nothing.

    How it works

    The problem

    A suite can report success even though none of its live cases ran. The result looks green while the behavior it was meant to check remains unknown.

    Why it happens

    The scoring path counted no failures without first requiring any completed work. An empty set therefore satisfied the success condition.

    How to work through it

    1. Report selected, completed, skipped and failed cases separately.
    2. Require the intended cases to complete before returning a passing verdict.
    3. Make missing credentials or unavailable prerequisites visible as a non-passing result.

    The solution

    A live evaluation with ten selected cases and ten marked “not run” should say it was not evaluated. It should never share the same success signal as ten completed, passing cases.

    How to check it

    Deliberately remove a required prerequisite and expect the gate to stop or report incomplete execution. Also run a known failing case to check that failure reaches the final verdict.

    Where this stops

    An intentionally optional suite can be skipped, but the caller must see that distinction. No completed cases means no evidence about the behavior under test.

  80. Software engineering

    Tag after the full gate. Push last.

    The release order that works: changelog, commit, tag locally, run the full gate, then push. It was found by running it, not by planning it.

    How it works

    The problem

    A release procedure can fail even when each individual command is valid. The full gate may depend on release metadata that was not present during an earlier check.

    Why it happens

    The order of operations changes the tree and the metadata being verified. A gate run before the local release state exists has checked a different state.

    How to work through it

    1. Prepare the changelog and commit the release tree.
    2. Create the release tag locally.
    3. Run the complete required gate against that local release state.
    4. Push the commit and tag only after the gate succeeds.

    The solution

    Treat the tag as local preparation until verification finishes. If a release check reads the version tag, it can now inspect the same version that would be pushed.

    How to check it

    Inspect the gate’s real result and confirm which commit the local tag resolves to. A successful earlier test run is not a substitute for this release check.

    Where this stops

    The original title is shorthand; the recorded procedure creates the local tag before the full gate and pushes last. Repositories with different release contracts may need another order.

  81. Software engineering

    To find why a tool refuses, change one input.

    Grepping its output for clues is guessing. Two runs that differ by a single input show the cause directly.

    How it works

    The problem

    A tool refuses an input, but its output contains several warnings. Matching one sentence does not show which condition caused the refusal.

    Why it happens

    Messages are written for people and can change. If two controlled runs differ in one input, their structured results give a cleaner causal signal.

    How to work through it

    1. Build a small input that is accepted.
    2. Copy it and change only the condition you want to test.
    3. Run both under the same environment and compare exit codes or structured result fields.

    The solution

    For an installer check, keep the destination and settings fixed, then add one invalid setting. If the accepted copy succeeds and the changed copy refuses, the test now targets that setting instead of guessing from wording.

    How to check it

    Restore the changed input and expect acceptance again. Keep unrelated warnings identical across both runs so they cannot explain the difference.

    Where this stops

    This isolates one condition only when other inputs and state stay fixed. Use text assertions when exact wording is itself a promised interface.

AI and agents

52 lessons

  1. AI and agents

    Write eval questions without borrowing the source wording.

    A search test is easier when its questions repeat the documents it searches. Ask in the reader’s words and score the facts a useful answer must contain.

    How it works

    The problem

    An answer key can look thorough while covering only familiar documents. If its author reads each source and turns a sentence into a question, the search system gets vocabulary clues that an ordinary reader may never supply.

    Why it happens

    The test is then partly measuring phrase matching. It can hide weak retrieval across several documents, and a fluent answer may pass even when it omits the facts the question actually needs.

    How to work through it

    1. Draft questions as someone with the problem would ask them. Keep the expected answer separate, with the source documents and a small list of facts that must be present.
    2. Include questions answered by one document, questions that need several documents, and questions the indexed material cannot answer. Score those groups separately.
    3. Check repeated wording and document reuse, then inspect the questions yourself. Treat phrase-overlap checks as prompts for review, because necessary technical terms may be shared.

    The solution

    Make the question natural and the answer key precise. Judge whether the key facts are supported, rather than whether the answer copies one preferred sentence.

    How to check it

    Before trusting the score, count the indexed documents and check which ones the answer set covers. For retrieval, report whether all required documents were found; a result depth the system did not return is not measured, rather than a measured zero.

    Where this stops

    The source records a prepared evaluation set; it leaves the owner’s first run and edits open. A no-answer question needs readable source text: an unprocessed scan cannot establish that the documents say nothing.

  2. AI and agents

    A streaming usage field may be a snapshot, not a total.

    Adding every usage field in a transcript can produce a neat total from incomplete counters. Cross-check one complete session before using that total to price a change.

    How it works

    The problem

    A cost study needs to separate input, cached input and output. A transcript may expose fields for all three, yet still record some of them before the response has finished. Summing those fields does not make them complete.

    Why it happens

    Streaming systems can record usage at different stages. An early event may know how much input was sent while knowing very little about the answer still being generated. The field name describes the quantity; it does not establish when the quantity was measured.

    How to work through it

    1. Choose a completed session and compare each usage category with the harness or provider total for that same session. Keep the model, time range and units the same.
    2. Check whether the transcript records a final event. Compare visible answer size as a rough sanity check, while keeping hidden reasoning and tokenisation differences separate.
    3. Use the authoritative final counters where available. If only partial telemetry survives, label its coverage. Call the cost a lower bound only after checking that no usage was counted twice.

    The solution

    Validate the counter before building the spreadsheet around it. Input telemetry can be useful even when output telemetry is incomplete, so judge the categories separately.

    How to check it

    For a new audit, reconcile one session end to end before aggregating many. A large unexplained gap means the total is not ready to support a savings claim.

    Where this stops

    The recorded study reported incomplete output counters; this is not a claim that every provider or transcript format behaves the same way. Character counts are estimates, and list-price calculations are not billed spend.

  3. AI and agents

    A skill's description says when, not how.

    The description decides when it fires. Put the steps in the body; steps in the description only blur the trigger.

    How it works

    The problem

    A skill may be selected correctly and still have its procedure skipped. If its description contains a miniature workflow, the agent can treat that summary as enough to act on.

    Why it happens

    The description is the routing surface: it helps choose when to load the skill. Mixing trigger conditions with abbreviated steps gives the reader a shortcut that omits the body's checks and decision points.

    How to work through it

    1. Write the description around the task symptoms and situations that should trigger the skill. Include a confusing near-match if it helps distinguish the skill from another available one.
    2. Put the ordered procedure, prerequisites and checks in the body. Keep one maintained process so the description cannot drift into an older, shorter version of it.

    The solution

    Separate finding the right instructions from following them. A good trigger gets the agent to the full procedure rather than trying to replace that procedure with a sentence.

    How to check it

    Test should-trigger and should-not-trigger prompts, then inspect a selected run to see whether the body was actually read and followed. Routing success and process compliance are separate results.

    Where this stops

    A clearer description cannot guarantee obedience, and overlapping skills may still compete. Report the scope and sample of any routing evaluation instead of treating a few successes as a universal firing rate.

  4. AI and agents

    Every instruction should answer a failure you saw.

    Rules written for imagined problems pile up and get ignored. Write the rule after the mistake, and say which one.

    How it works

    The problem

    An agent rule file can grow with plausible warnings until the important rules become hard to find. Advice without a real case is difficult to test and difficult to remove.

    Why it happens

    A rule spends attention on every task where it is loaded. Without a specific failure behind it, nobody can tell whether it prevents a recurring problem or merely sounds cautious.

    How to work through it

    1. Capture the observed mistake and the condition that made it possible. Write the smallest correction that would have changed that case, including the scope where it applies.
    2. Ask whether a script, test or hook can enforce the correction. Put deterministic checks there and leave the agent the judgement that cannot be reduced to a reliable check.

    The solution

    Keep the rule, its reason and the case connected. This lets a future reviewer narrow or retire the rule when the workflow changes.

    How to check it

    Replay the original failure as a fixture where practical. The new check should reject it while accepting a nearby valid case, so a broad prohibition does not quietly replace the real correction.

    Where this stops

    Some preventative controls are justified by external requirements or known hazards before a local failure occurs. Name that basis explicitly instead of inventing an observed incident.

  5. AI and agents

    A neutral API doesn't mean neutral limits.

    One chat API fronts many vendors, yet one model family rejected max tokens below 16 while others accepted 1. Test every model.

    How it works

    The problem

    A provider-neutral model API can accept the same request shape while different models reject different parameter values. Testing one model therefore leaves compatibility failures hidden until another is selected.

    Why it happens

    The common interface translates the request, but the model provider still validates its own limits. In the reported example, a one-token output limit worked for some model families and failed for another.

    How to work through it

    1. Build a small probe for every model exposed by the application. Keep the request within documented limits and record validation failures separately from access or availability failures.
    2. Choose shared defaults from the supported range, then store any model-specific constraints in configuration. Record the reason for a changed default so a later cleanup does not undo it.

    The solution

    Preserve the common adapter while testing the differences behind it. The reported probe needed an output allowance of 16 tokens for the model family that rejected 1.

    How to check it

    Repeat the compatibility sweep when adding models or changing parameters. Every configured model should accept the supported minimal request, or be clearly excluded with its reason.

    Where this stops

    A minimal successful response proves invocability, not quality or complete feature parity. The reported floor is historical; new models and API changes can introduce different constraints.

  6. AI and agents

    A one-line agent call isn't a small call.

    Most of its cost is the agent writing its own long system prompt to cache. Tiny requests carry a fixed overhead.

    How it works

    The problem

    A one-line request to an agent can cost more than its visible prompt suggests. Estimating from the user's sentence misses the context the agent framework sends before it can answer.

    Why it happens

    The request may include a long system prompt, tool definitions and a cache write. Those are input costs even when the requested answer is only a few words.

    How to work through it

    1. Measure the whole invocation with token and cost telemetry. Separate ordinary input, cache creation, cache reads and output so the fixed overhead is visible.
    2. Compare a fresh invocation with a warm repeat under the same configuration. Use the actual billing categories rather than assuming all cached tokens have the same price.

    The solution

    Choose the smallest execution path that can do the task. A direct structured call may suit a tiny operation; an agent is worthwhile when its tools and judgement earn their setup cost.

    How to check it

    Check whether reducing the user prompt changes total billed work meaningfully. The expected result is a cost breakdown that explains the bill, including context outside the visible request.

    Where this stops

    Cache behaviour depends on provider, lifetime and exact prompt reuse. One reported call does not establish a universal token overhead, and saving cost must not remove context needed for correctness.

  7. AI and agents

    Random inputs need a golden model.

    Constrained random works in verification because a reference model knows the right answer. For LLM output, that means an eval set with known answers, or a judge.

    How it works

    The problem

    Generating many varied inputs does not tell you whether a language model answered correctly. A large test run can produce plenty of output while offering no basis for accepting any of it.

    Why it happens

    Constrained random testing works in hardware because a reference model supplies the expected answer. Open-ended model output needs an equivalent judgement rule before variation becomes useful evidence.

    How to work through it

    1. Start with a small evaluation set whose expected answers or acceptance criteria are known. Include failures that matter to the task, such as a missed confidential term or an incorrect refusal.
    2. Where exact answers are unsuitable, define a judge rubric and check it against examples a person has labelled. Keep disagreements visible rather than treating the judge as unquestionable truth.

    The solution

    Use varied inputs to explore a defined correctness boundary. Preserve failing examples so the next change can be checked against the same cases.

    How to check it

    Plant a clearly wrong output and confirm the evaluation rejects it. Then inspect borderline cases to see whether the scoring rule matches the intended task.

    Where this stops

    A judge can share the model's blind spots, and a fixed set can be overfit. Keep some examples aside and report what the evaluation covers rather than calling the model generally correct.

  8. AI and agents

    An LLM is a probability machine.

    Output quality follows input quality, so the real work is the grounding and orchestration around the model.

    How it works

    The problem

    A model can produce a fluent result that has no dependable relationship to the task's evidence.

    Why it happens

    Statistical generation responds to its context. Missing grounding and vague handoffs leave the model to supply likely text, while repeated prose handoffs can change meaning between steps.

    How to work through it

    1. Give each step the evidence and contract it needs.
    2. Use deterministic code for decisions that can be specified exactly.
    3. Reserve model judgment for the remaining ambiguous work and check its output before the next step.

    The solution

    Illustrative: a source-selection step should return structured document identities and evidence, rather than a persuasive paragraph that the next agent has to reinterpret as facts.

    How to check it

    Trace a representative answer back through its selected sources and validation steps. Missing evidence should produce a visible gap rather than an invented completion.

    Where this stops

    Better context does not guarantee truth. Schema validity proves structure, not factual support, and judgment steps still need appropriate review or comparison against known cases.

  9. AI and agents

    Better input means better curated, not more.

    Adding a reranker made retrieval worse, and navigation chunks caused refusals. Grounding is curation.

    How it works

    The problem

    Retrieval can get worse after adding a ranking stage if the corpus contains navigation text and competing versions of the same evidence.

    Why it happens

    A reranker can only reorder what it receives. It cannot turn irrelevant or misleading source chunks into a clean evidence set, and repeated navigation can dominate the available context.

    How to work through it

    1. Inspect retrieved chunks before changing the model.
    2. Remove or separate navigation, obsolete versions and low-value repeated material.
    3. Evaluate corpus changes and ranking changes independently against the same questions.

    The solution

    Illustrative: a top result containing only a menu and page links offers little evidence for a technical answer. Removing that chunk can be more useful than making its ranking score more precise.

    How to check it

    Run retrieval-only cases and inspect relevance, source identity and useful answer content. Keep a ranking change only if the observed evidence improves for the intended tasks.

    Where this stops

    Curation can also remove needed context. Preserve document provenance and test difficult queries, rather than assuming that a smaller corpus or another ranking stage is always better.

  10. AI and agents

    An agent that writes to its own knowledge base can poison it.

    Route its writes through an inbox and a review gate, with provenance tags, before they become things it answers from.

    How it works

    The problem

    An answering agent can make its own unsupported statements look authoritative by writing them into the corpus it later searches.

    Why it happens

    Retrieval provides provenance only if the underlying content has earned its place. Direct write-back can turn generated guesses into future evidence and create a feedback loop.

    How to work through it

    1. Route proposed additions to an inbox outside the trusted corpus.
    2. Attach source provenance and distinguish generated interpretation.
    3. Review the proposal before promotion and ingestion.

    The solution

    Illustrative: a meeting summary can be staged as a proposed note with timestamped source references. Until reviewed, it should not become a fact the answering agent cites as company knowledge.

    How to check it

    Trace a proposed write through staging, review and ingestion. An unreviewed statement should remain unavailable to the trusted-answer path, while approved material retains its provenance.

    Where this stops

    This is a design safeguard in the source, not evidence that the complete agent was deployed. Review also needs access controls and a way to correct or withdraw already ingested material.

  11. AI and agents

    Understanding beats acceptance.

    I don't need every detail, but I need the mental model before I trust a system. A true black box is a liability.

    How it works

    The problem

    A component can be easy to call while remaining too poorly understood to maintain or trust.

    Why it happens

    An interface hides implementation detail, but it does not remove assumptions about inputs, state and failure. Without that rough model, a caller cannot tell when the abstraction's promises stop applying.

    How to work through it

    1. Draw the component's inputs, outputs and state boundary.
    2. Explain its normal path and its important failure modes.
    3. Check its contract independently before depending on hidden internals.

    The solution

    Illustrative: a storage adapter may hide its database implementation, while the caller still needs to know whether a write is atomic and how failure is reported. That is enough model to reason about the boundary.

    How to check it

    Ask another reader to predict the outcome of a normal call, a missing input and a failure using the contract alone. Unanswerable cases identify gaps in understanding.

    Where this stops

    Understanding every internal line is unnecessary. The goal is a usable model and an earned contract, not a requirement to reimplement each dependency before using it.

  12. AI and agents

    Old versions can outrank the current doc.

    With several versions in one index, the top hits were drafts 0.5, 0.4 and 0.2 instead of 1.1. Clean out old versions before loading.

    How it works

    The problem

    Retrieval can confidently cite an obsolete draft when several versions of the same document share one index.

    Why it happens

    Similarity ranking does not inherently prefer the current revision. Repeated old text can occupy several top results and crowd out the document that now owns the answer.

    How to work through it

    1. Choose an authoritative version before ingestion.
    2. Remove superseded copies from the searchable current corpus or place them behind an explicit history filter.
    3. Inspect returned document identities and versions before judging the generated answer.

    The solution

    Illustrative: three similar drafts can fill the top matches while a current version ranks below them. Cleaning the corpus addresses the evidence selection before changing the model prompt.

    How to check it

    Run a retrieval-only question set and inspect the versions of the returned documents. Current-answer queries should resolve to the intended current source.

    Where this stops

    Historical research may need old versions. Retain them with explicit version metadata and scope rather than deleting the only record of past decisions.

  13. AI and agents

    Back off in the tool, not in the model.

    Show an LLM a raw 429 and it invents its own retry plan and narrates it. Rate limits are a platform job.

    How it works

    The problem

    A large API summary can spend its time recovering from rate limits instead of answering the question.

    Why it happens

    If raw 429 responses reach the model, it may improvise retries. The request budget is shared across sessions, so one conversation cannot reliably manage that platform constraint.

    How to work through it

    1. Put bounded retries and backoff inside the API tool.
    2. Use the endpoint's supported page size to reduce requests.
    3. Stop at a defined budget floor and return an honest partial sample when necessary.

    The solution

    Illustrative: increasing a supported page size can turn many small page requests into fewer large ones. The tool can then return the actual sample size instead of letting the model imply complete coverage.

    How to check it

    Inject a rate-limit response and a dropped connection. Expect bounded recovery, no duplicate page accumulation and an explicit partial-result signal when the budget is exhausted.

    Where this stops

    Retries do not create more capacity. Respect endpoint semantics and avoid replaying non-idempotent writes without a separate duplicate-protection design.

  14. AI and agents

    Isolation beats a shared soup of state.

    Multi-agent works when each agent gets a bounded context and a clean handoff. Without that discipline, one agent does better.

    How it works

    The problem

    Parallel agents can produce conflicting work when they share mutable notes and assumptions without clear task boundaries.

    Why it happens

    More workers do not automatically create more independent progress. If every agent must infer which state is current, coordination can consume the benefit of parallel execution.

    How to work through it

    1. Give each agent a bounded task, the evidence it needs and an explicit output contract.
    2. Define ownership of shared artifacts and a handoff point where results are reconciled before dependent work continues.

    The solution

    For two modules, let each worker own its implementation while agreeing on the interface first. A handoff should name the exact files, checks and unresolved questions instead of assuming the next agent remembers the conversation.

    How to check it

    Ask whether a fresh agent can complete one assigned task from its handoff alone. At integration, check conflicting edits and interface assumptions before treating the outputs as one working feature.

    Where this stops

    Isolation still needs coordination at shared boundaries. Some tasks are too coupled to split economically; one well-contextualised agent can be the simpler choice.

  15. AI and agents

    Rules held by checks survive. Prose rules drift.

    Across audits, requirements backed by a check stayed true and prose-only governance didn't. Move rules into checks.

    How it works

    The problem

    A rule written in a guide can disappear from daily practice while the guide still claims the project follows it.

    Why it happens

    Prose depends on each reader remembering and interpreting the rule at the right moment. A required check can make a decidable violation visible on the acceptance path.

    How to work through it

    1. For each rule, ask what concrete artifact proves compliance and whether code can decide it reliably.
    2. Put decidable rules into the normal gate, keep judgment questions in review, and add a deliberately broken example proving the gate rejects the violation.

    The solution

    For a required metadata field, validate its presence and type instead of repeating “remember the field” in more documents. Keep a link to the reason so future maintainers know what the check protects.

    How to check it

    Run the negative example through the same gate used for real work. Confirm a non-zero result and a specific diagnostic, then confirm a valid case is accepted.

    Where this stops

    Checks can encode the wrong rule or be disconnected from real acceptance. They do not replace judgment, and the historical audit claim in the original card is a source report rather than a new comparison here.

  16. AI and agents

    Close the loop like a control system.

    Agents should check their own output and correct against the error, the way a controller corrects against a sensor. Open-loop agents drift.

    How it works

    The problem

    An agent that produces output and immediately declares completion can repeat the same mistake without ever seeing the error.

    Why it happens

    A corrective loop needs a target, an observation of the result and a comparison that changes the next action. Asking for “better work” supplies none of those on its own.

    How to work through it

    1. Choose the observable acceptance condition and run the check against the produced artifact.
    2. Feed the specific failing evidence back into a bounded correction step, then repeat the check on the revised artifact.

    The solution

    For a generated configuration, parse and validate the actual file before asking the agent to fix any reported field. Keep the validator independent of the prose claim that the file is valid.

    How to check it

    Deliberately include one invalid value in a disposable example. Expect the check to reject it and identify the field; completion should require a fresh check of the corrected bytes.

    Where this stops

    A faulty sensor drives a faulty loop. Human judgment still matters for goals and uncheckable quality, and retry limits must surface unresolved failure rather than looping indefinitely.

  17. AI and agents

    A score can be perfect while the output is wrong.

    Word recall was 1.0 while list nesting and emphasis were broken. A word count can't see structure; structure needs its own check.

    How it works

    The problem

    A document converter can preserve every word while damaging list nesting, emphasis and the relationships that make the document readable.

    Why it happens

    Word recall measures retained text. It cannot tell whether a phrase moved into the wrong list item or whether a heading was flattened into ordinary prose.

    How to work through it

    1. Keep text-retention checks, then add a separate reference for headings, lists, tables, links and emphasis.
    2. Derive that reference independently from the source format, rather than copying the converter’s own assumptions into its grader.

    The solution

    For a nested list, compare which item owns each child, not merely whether all words appear. Give unsupported formatting an explicit receipt instead of counting silent flattening as faithful conversion.

    How to check it

    Use a fixture with identical words in two different nesting structures. The word score may match, but the structure check should distinguish them and identify the changed relationship.

    Where this stops

    A structure score is only as good as the facts and reference it checks. Independent extraction reduces shared mistakes but still needs review for omitted source semantics.

  18. AI and agents

    Long context is paid again every turn.

    The whole context is re-sent on each turn, which raises cost and degrades answers. Default to a modest window and opt in to the huge one.

    How it works

    The problem

    A growing conversation can make a short follow-up expensive because the next model call also carries material from earlier turns.

    Why it happens

    Stateless agents need context supplied on each call. A larger allowed window gives the caller room to send more; it does not make every old detail useful to the current question.

    How to work through it

    1. Set a routine context budget and assemble the current task, relevant evidence and recent turns deliberately.
    2. Use retrieval or a labelled summary for older material, and expand the budget explicitly when the task needs the complete source.

    The solution

    Keep a short preamble and record what was included. If required evidence does not fit, split the task or refuse the incomplete review instead of silently clipping the source.

    How to check it

    Inspect request token counts and actual retained evidence over several turns. For a known question, check whether the bounded context still contains its supporting facts before comparing answer quality.

    Where this stops

    Resent context may receive provider caching discounts, so cost is not always full-price each turn. Large windows do not invariably degrade answers, and summaries can lose essential detail.

  19. AI and agents

    Build tools around the user's question, not the API's pages.

    Paging through raw records burns tokens and context. A tool that answers 'how many, grouped by what' directly is cheaper and harder to misuse.

    How it works

    The problem

    An agent asked for a monthly summary can spend its context fetching pages of records instead of answering the question.

    Why it happens

    A record-listing API exposes storage access, not the aggregation the user needs. Asking the model to collect and count everything makes pagination, completeness and arithmetic part of its reasoning burden.

    How to work through it

    1. Translate the question into an explicit time range, grouping and measure, then implement that calculation beside the data access code.
    2. Return the grouped answer with the total population, inspected coverage and any unavailable fields, rather than returning an unbounded record dump.

    The solution

    For “how many tickets by category over 30 days,” expose a bounded aggregation tool. Leave interpretation and possible causes to the model after deterministic counting is complete.

    How to check it

    Compare the tool with a small known dataset and reconcile group totals with the declared population. Simulate a missing API page and expect partial coverage to remain visible.

    Where this stops

    Aggregation cannot answer every qualitative question. Keep a bounded way to inspect representative records, and do not describe incomplete collection as a complete report.

  20. AI and agents

    Caveats only work if the model reads them.

    'Sample too small' flags do nothing if the agent stops at the headline number. Sometimes you have to force a second step.

    How it works

    The problem

    A report can carry a warning about incomplete data and still be summarised as a confident headline number.

    Why it happens

    The caveat is only information until the consuming step acts on it. A model can overlook a flag or quote the statistic without carrying its conditions into the answer.

    How to work through it

    1. Make the tool calculate coverage and sample validity itself, and return explicit reasons when a statistic is not supported.
    2. Require the reporting step to inspect that status before it can use the number; for stronger guarantees, withhold invalid statistics in deterministic code.

    The solution

    For a small contaminated sample, return “not enough data” with the count and reason rather than a median plus a warning the next step may drop.

    How to check it

    Feed a report with a tempting headline and an invalid-sample flag through the consumer. Expect it to refuse the statistic and preserve the reason in the user-facing explanation.

    Where this stops

    A second model step can improve attention but cannot guarantee compliance. A code-enforced validity boundary is stronger when the rule can be decided mechanically.

  21. AI and agents

    GPU memory utilisation is a target, not a cap.

    The same vLLM setting gave different KV cache sizes on different starts. Pin the cache in bytes when you need a fixed footprint.

    How it works

    The problem

    Two starts with the same vLLM memory fraction can leave different amounts of memory available for requests, making a shared card difficult to budget.

    Why it happens

    The key/value cache holds attention data for request tokens. When its size is the remainder after startup measurements, a fixed memory fraction need not produce a fixed cache allocation.

    How to work through it

    1. Choose a cache budget with the other servers accounted for, and set kv-cache-memory-bytes on a version that supports it.
    2. Keep the total budget for weights, working memory and all caches within the card’s capacity, with room for startup and other users.

    The solution

    Pin the cache in bytes when repeatable allocation matters. Treat the fraction as planning information once the runtime uses an explicit cache size.

    How to check it

    After each restart, inspect the reported cache bytes and available token capacity. After an upgrade, check both again; the same byte budget may hold a different number of tokens.

    Where this stops

    A cache pin fixes one allocation, not the whole process footprint or an operating-system-enforced GPU cap. Context length and concurrency still have to fit.

  22. AI and agents

    A reranker only reorders what retrieval found.

    It scores query and candidate together, but if the right chunk wasn't in the first-stage top-N, no amount of reranking brings it back.

    How it works

    The problem

    An answer can remain wrong after a better reranker is installed because the passage containing the answer never reached that model.

    Why it happens

    The first stage chooses candidates; the reranker scores only those candidates against the query. Its improved ordering cannot add a missing passage.

    How to work through it

    1. For a question with a known supporting passage, save the first-stage candidate list before reranking.
    2. Check whether the passage is present. If absent, improve candidate coverage through chunking, search method or shortlist size before tuning the second stage.

    The solution

    Debug retrieval coverage separately from ranking quality. For example, moving the right passage from rank 30 to rank 2 helps only if the first stage returns at least 30 candidates.

    How to check it

    Measure how often known supporting passages enter the candidate set, then how often reranking places them in the final context. Those checks locate different failures.

    Where this stops

    A larger shortlist costs time, and better retrieval does not guarantee a faithful answer. Keep answer grounding as a separate check after ranking.

  23. AI and agents

    When an agent fails something simple, check its context.

    A plain SQL aggregation shouldn't fail. When it does, the cause is usually missing schema or bad orchestration, not the model.

    How it works

    The problem

    A small SQL aggregation can fail even when the model knows how to write SQL. Repeating the request with a larger model may leave the real gap untouched.

    Why it happens

    The agent may lack table definitions, time-window rules or a way to inspect an error. Without those inputs it is solving a different problem from the one the operator has in mind.

    How to work through it

    1. Give the agent the relevant schema, units and one example row, with the exact aggregation and time range requested.
    2. Ask it to show the query and inspect the returned error or a small sample before producing the final summary.

    The solution

    For a 30-day total, make the date field, timezone and treatment of missing values explicit. Feed query errors back into the same bounded task instead of restarting without them.

    How to check it

    Compare a small, hand-checkable subset with the query result. Then inspect the complete query’s filters before trusting the larger total.

    Where this stops

    Context is a useful first diagnosis, not proof that model capability never matters. Reassess after the task, permissions and feedback path are complete.

  24. AI and agents

    Without a reasoning parser, thinking leaks into answers.

    Some models close their thinking with </think> but never open it. The server needs the matching parser to split the thought from the reply.

    How it works

    The problem

    A client expecting only an answer can receive the model’s reasoning text, or receive no answer when the token budget runs out before the answer starts.

    Why it happens

    The model’s output format and the server’s parser have to agree. A closing thinking marker without a matching opening marker can defeat a parser chosen for a different template.

    How to work through it

    1. Inspect the model’s documented chat template and choose the matching reasoning parser in the serving configuration.
    2. Send a short diagnostic request and inspect the structured reasoning field, answer field and finish reason separately.

    The solution

    Keep reasoning and the final reply in separate response fields. Budget for both phases rather than setting the token limit to the desired answer length alone.

    How to check it

    The expected response has answer text in message.content and reasoning in its separate field. A length finish reason with empty content calls for a budget check before blaming the client renderer.

    Where this stops

    Parser names and response shapes depend on the model and server version. Do not apply one model’s parser setting to every model.

  25. AI and agents

    The key RAG test is a question it can't answer.

    Ask something outside the corpus. The system must say 'not found' rather than invent, because the grounding layer is where quality is won.

    How it works

    The problem

    A document-answering system can look impressive when every question has an easy answer in its documents. That test set does not show whether the system knows when its evidence is missing.

    Why it happens

    Retrieval and answer writing are separate stages. A language model can produce a plausible answer from weak or unrelated passages, so fluent output is not a reliable signal that the documents support it.

    How to work through it

    1. Build a small evaluation set with questions whose answers are in the corpus and questions whose needed facts are absent. Keep the expected support separate from the generated answer.
    2. Inspect the retrieved passages for each question before grading the response. Decide whether retrieval found the evidence and whether the response stayed within it.
    3. Require an explicit insufficient-evidence result for the unsupported questions. Repeat the same set when changing the retriever, chunking or model.

    The solution

    Test the edge of the system's knowledge as deliberately as its happy path. A grounded refusal can be the correct output even when the model could write a longer answer.

    How to check it

    For an illustrative absent-fact question, expect the system to say it cannot establish the answer from the corpus. It should not turn a loosely related passage into a citation for the missing fact.

    Where this stops

    A refusal alone is not enough: retrieval may have missed a real answer. Grade retrieval and answer faithfulness separately before calling the result correct.

  26. AI and agents

    If the handoff isn't enough, the handoff is the bug.

    An agent should finish a task from the handoff alone. Missing context isn't the agent's failure, and it multiplies token cost.

    How it works

    The problem

    An agent handoff can appear detailed while omitting the decision or artifact needed to make the next step executable.

    Why it happens

    The receiving agent does not share the author’s unstated context. Missing input versions, acceptance criteria or stop conditions force it to search, guess or ask for work already understood elsewhere.

    How to work through it

    1. State the goal, current state and exact next deliverable.
    2. Include the source locations, relevant decisions and checks needed to act.
    3. Name unresolved questions and failure conditions rather than leaving them implicit.

    The solution

    For example, “finish the parser” is weaker than a handoff naming the supported grammar, changed files, remaining failing fixture and command that checks it. The latter gives the next worker a bounded problem.

    How to check it

    Give the handoff to a fresh reader and ask them to identify their first action and definition of done. Any required hidden fact is a concrete repair to the document.

    Where this stops

    Self-contained does not mean copying the entire project. Stable links and concise relevant context can be enough, and the source’s cost warning is not a measured universal growth formula.

  27. AI and agents

    AI is slightly over-hyped, and mostly misused.

    People ask it to do their whole job. It earns its keep as one bounded step inside a larger, mostly deterministic process.

    How it works

    The problem

    Asking a model to own an entire job can hide which decisions need judgment and which should already have a precise answer.

    Why it happens

    An open-ended request combines planning, execution and checking in one uncertain loop. Routine steps then become fresh language-model decisions instead of repeatable operations.

    How to work through it

    1. Choose a bounded task where interpretation adds value.
    2. Give it explicit input, output and acceptance rules.
    3. Let deterministic code own the surrounding data handling, execution and result recording.

    The solution

    For example, a model can propose an explanation for a failed check while a script gathers the logs and a real test evaluates the proposed repair. The model’s answer is useful input to the process, not its final evidence of success.

    How to check it

    Compare complete-task outcomes and correction effort across representative cases. Check that a wrong or incomplete model answer is caught before the workflow reports completion.

    Where this stops

    This is a design preference, not a measured claim that AI is overvalued in every domain. Some tasks need more exploration, but they still benefit from an explicit way to judge the result.

  28. AI and agents

    Models write broken JSON. Parse loosely, check strictly.

    Raw line breaks and invalid escapes are common in model output. Tolerant parsing followed by strict validation keeps good answers without trusting bad ones.

    How it works

    The problem

    A useful model answer can fail JSON parsing because of a formatting defect, while a permissively parsed answer can still contain invalid data.

    Why it happens

    Parsing and validation solve different problems. A tolerant parser may recover a representation, but only strict validation can decide whether its fields, types and values meet the consumer’s contract.

    How to work through it

    1. Preserve the raw model response for diagnosis.
    2. Apply only a defined, bounded set of syntax repairs.
    3. Validate the recovered object strictly before any downstream action, and reject ambiguous recovery.

    The solution

    For example, repairing a raw line break inside a string should not authorize an unknown action field. Keep the original response and record the repair so a later failure can be traced to what the model actually returned.

    How to check it

    Replay captured malformed answers and planted invalid fields through the real parser and validator. Confirm recoverable syntax succeeds while wrong shape and meaning still fail.

    Where this stops

    Syntax repair can change meaning, so keep it narrow. Some applications should retry or reject malformed output entirely; tolerant parsing is not permission to guess missing facts.

  29. AI and agents

    Instructions loaded early fade by decision time.

    Later context dilutes early instructions. Inject the rule at the point where the decision is made, not only at the start of the prompt.

    How it works

    The problem

    An instruction loaded at session start may have little practical effect when the relevant decision happens much later in a long context.

    Why it happens

    The model must select useful context among many later details. A rule far from the action can be overlooked even when it technically remains in the prompt.

    How to work through it

    1. Identify the decisions where a particular rule must influence behavior.
    2. Place a short, relevant reminder or retrieved rule beside that decision.
    3. Keep authoritative rule text in one owner so reminders do not become conflicting copies.

    The solution

    For example, a reviewer choosing whether a claim is supported should receive the evidence-status definitions with the claims it is judging. A general opening reminder to be careful is less specific to that choice.

    How to check it

    Test representative long-context cases with the rule only at the start and with the relevant reminder near the decision. Compare actual rule compliance rather than prompt length alone.

    Where this stops

    Context placement is a reliability aid, not a security boundary or a guaranteed attention rule. Excessive reminders can consume budget and create their own noise.

  30. AI and agents

    Soft wording gives an agent permission to skip.

    A protocol step loses to the main task unless it is written as a commitment, with a named trigger and stop behaviour.

    How it works

    The problem

    An agent can treat a protocol step as optional when the instruction leaves its trigger or failure behavior open to interpretation.

    Why it happens

    Words such as “consider” describe a preference. During a competing main task, a preference may be skipped because the model has no clear condition that makes the step mandatory.

    How to work through it

    1. Name the event that triggers the rule.
    2. State the required action and the evidence that counts as completion.
    3. State what to do if the action cannot complete, including any required stop.

    The solution

    For example, “Before reporting completion, run the named check; if it cannot run, report the blocker and do not claim a pass” gives the agent a decision it can apply. “Try to test” leaves the important branch unspecified.

    How to check it

    Exercise the instruction with a successful check and a missing prerequisite. Inspect whether the agent reports the actual result and follows the defined stop behavior.

    Where this stops

    Stronger wording helps clarify intent, but a prompt remains guidance. Put high-consequence requirements in enforceable gates rather than relying on capitalization or repetition alone.

  31. AI and agents

    The grader must not share code with the thing it grades.

    If the checker and the converter use the same extraction, their mistakes cancel and the score reads perfect.

    How it works

    The problem

    A converter and its checker can agree perfectly because both make the same extraction mistake.

    Why it happens

    If the expected result comes through the same parser or intermediate conversion as the output under test, a shared loss appears on both sides and cancels out of the comparison.

    How to work through it

    1. Trace how the output and expected result are produced.
    2. Read the original source independently for the properties being checked.
    3. Use damaged fixtures that alter text, order and structure separately.

    The solution

    For example, if both paths reorder slides by filename, comparing their text will not expose the wrong presentation order. The expected sequence must come from the presentation’s own ordering relationships.

    How to check it

    Plant a one-sided defect and a shared-helper defect in disposable fixtures. The check should reveal whether its independence is real or only assumed.

    Where this stops

    An independent checker is still an implementation with blind spots. Token recall also ignores extra text and order, so a perfect recall score must not stand in for structural fidelity or full correctness.

  32. AI and agents

    torch.compile needs a modern C++ compiler.

    TorchInductor compiles kernels at runtime, and an old gcc 8.5 can't build them. Disabling Dynamo works, but compare eager output against CI before trusting it.

    How it works

    The problem

    A library can fail at runtime even after its Python dependencies installed successfully, because it generates native kernels on the host.

    Why it happens

    TorchInductor uses a C++ toolchain during compilation. An older compiler may not support the generated code, so the failure comes from the environment beneath the application.

    How to work through it

    1. Inspect the compiler error and identify the compilation path actually being used.
    2. Choose a supported compiler or a documented eager-execution workaround.
    3. Apply that choice in the lane’s environment so local runs and CI use a deliberate policy.

    The solution

    For example, disabling Dynamo can avoid an unsupported compilation path. That restores eager execution; it should be evaluated as a different execution mode, with its output and performance checked rather than assumed.

    How to check it

    Run the same representative inputs through the chosen mode and compare against the established baseline. Keep pre-existing failing cases visible instead of renaming a matching mixed result as a complete pass.

    Where this stops

    Compiler requirements vary with PyTorch, backend and generated kernels. The old-compiler failure in the source is a particular environment, not a universal minimum-version rule.

  33. AI and agents

    A rule nobody receives does nothing.

    Distribution matters more than how good the rule is. Put it where the agent already reads, or it doesn't exist.

    How it works

    The problem

    A carefully written rule has no effect when the agent doing the work never loads it.

    Why it happens

    Authoring and delivery are separate parts of the system. A file in a vault can be correct while a fresh working session sees only its installed instructions and discoverable skills.

    How to work through it

    1. Package the rule where the intended agent already looks for guidance.
    2. Give it a stable name, trigger and version that a session can inspect.
    3. Verify loading in a fresh session instead of relying on the author’s current context.

    The solution

    For example, a writing deck can be distributed as a skill that points to the current rules and examples. The useful test is whether a new session can find the relevant instruction when the task requires it.

    How to check it

    Inspect the loaded package and try a small task that exercises a specific rule. Check the baseline first: a failing baseline can make every mutation appear successfully caught.

    Where this stops

    Distribution does not guarantee understanding or compliance. It closes the missing-context gap; enforcement and review still need evidence of their own.

  34. AI and agents

    Natural language is the front door, not the plumbing.

    Humans talk to agents in prose. Agents should talk to each other in schemas, because prose degrades with every hop.

    How it works

    The problem

    An agent chain can lose meaning when every stage interprets and rewrites the previous stage’s prose.

    Why it happens

    Natural language leaves room for different readings. A schema makes required fields and permitted values explicit, so a handoff can be validated before another program acts on it.

    How to work through it

    1. Let the human describe the goal in ordinary language.
    2. Translate that goal into a checked contract with explicit unresolved questions.
    3. Pass structured results between stages and reject missing or invalid fields.

    The solution

    For example, a task handoff can name an input artifact, required operation and acceptance check as separate fields. The next stage should not need to infer those from a paragraph that mixes instructions with background.

    How to check it

    Replay an accepted handoff through the consumer and test one with a missing requirement or unknown status. Validation should stop it before execution, with a useful error.

    Where this stops

    A schema checks shape, not truth or completeness. Human judgment still has a role in resolving ambiguous intent, and prose remains useful as explanation alongside the fields.

  35. AI and agents

    Size images to the model's training geometry.

    Feeding 300 DPI pages to a vision retriever trained on small fixed-size images explodes patch count, time and VRAM. Resize first and batch to bound memory.

    How it works

    The problem

    A page-image retrieval job can exhaust memory because its input resolution is far larger than the model’s useful working scale.

    Why it happens

    Vision models divide images into patches or similar units. More pixels can mean many more units per page, and batching multiplies their memory cost without guaranteeing a better retrieval result.

    How to work through it

    1. Read the model’s supported preprocessing and resolution guidance.
    2. Resize pages while preserving aspect ratio and enough detail for the task.
    3. Use an explicit batch bound and measure memory on representative pages.

    The solution

    For example, a high-resolution scan can remain the archival original while a smaller derived image feeds retrieval. Keep the two roles separate so reducing inference cost does not destroy the source.

    How to check it

    Compare retrieval on small text, tables and diagrams at the selected size, then measure peak memory for the intended batch. Check the hardest pages rather than only an average page.

    Where this stops

    There is no universal DPI or pixel limit. The model, preprocessing and page content determine the trade-off, so a smaller image must earn its quality as well as its memory saving.

  36. AI and agents

    Prompt caching is the biggest lever on agent cost.

    Agent sessions resend the same long prompt every turn. Cached reads cost a small fraction of fresh input, so most of the bill falls away.

    How it works

    The problem

    A long agent session can spend much of its input budget rereading instructions and context that have not changed.

    Why it happens

    Prompt caching lets a provider reuse an eligible repeated prefix at a lower input price. The saving depends on how much is reused, cache lifetime and the provider’s current pricing.

    How to work through it

    1. Put stable instructions and reusable context before frequently changing material.
    2. Use the provider’s supported cache boundaries and record cache-read and cache-write tokens.
    3. Compare total cost over a representative session, including the initial cache write.

    The solution

    For example, a stable rule deck followed by the current task can reuse more input than a prompt that inserts changing status text before the deck. Check actual token categories rather than inferring a hit from identical-looking prompts.

    How to check it

    Compare uncached input, cache writes and cache reads for similar workloads using the correct model prices. Keep output cost separate so the source of the saving remains visible.

    Where this stops

    Caching is not always the largest lever. Short sessions, changing prefixes and expired entries may gain little, and model choice or smaller context may matter more.

  37. AI and agents

    Script 80%. Let the model judge 20%.

    Agents inside a mostly scripted workflow are predictable. The boring version, deterministic steps with AI in the loop, beats the fully autonomous one.

    How it works

    The problem

    An open-ended agent workflow makes routine work depend on repeated model decisions, so the same task can take a different path each time.

    Why it happens

    Many steps already have a precise answer: validate a schema, find changed inputs, run a command, or compare results. Giving those decisions to a model adds variation without adding useful judgment.

    How to work through it

    1. Separate exact operations from decisions that need interpretation.
    2. Script the exact operations and give the model bounded inputs and outputs.
    3. Validate the model’s result before the next scripted step acts on it.

    The solution

    For example, code can collect failing tests and their logs; a model can suggest a cause; code can then run the selected check and record what happened. The workflow owns progress and failure handling.

    How to check it

    Replay a known case and check that routing, validation and stop behavior remain predictable. Test malformed or incomplete model output as well as a plausible answer.

    Where this stops

    The 80/20 split is a design heuristic, not a measured ratio or a requirement. Tasks differ in how much judgment they need, and deterministic scaffolding does not make an incorrect model answer true.

  38. AI and agents

    The instrument needs its own instrument.

    Whatever a checker doesn't compare can regress in silence. Point a check at the grader too, or its blind spots become yours.

    How it works

    The problem

    A quality score can remain high while an important property of the output gets worse.

    Why it happens

    A checker measures only the questions it asks. Text recall, for example, can stay perfect when a converter changes table structure or slide order, because all the words remain present.

    How to work through it

    1. List the kinds of damage the checker is supposed to detect.
    2. Build small examples with one known defect at a time.
    3. Confirm that each defect changes the relevant verdict, and add an independent comparison where practical.

    The solution

    Treat the grader as another system under test. An illustrative table with its columns swapped should fail a structural check even when its text score stays unchanged. The two checks answer different questions.

    How to check it

    Keep a clean control beside each damaged fixture. The checker should accept the clean case and reject the damaged one for the intended reason.

    Where this stops

    A mutation proves sensitivity to that mutation, not complete coverage. Independent implementations can still share assumptions, so agreement alone should not be presented as a correctness proof.

  39. AI and agents

    A reasoning model with a low token limit returns nothing.

    It spends the budget thinking and has no answer left. The empty content looks like success; the reasoning sits in a separate field.

    How it works

    The problem

    A reasoning-model request can return a successful transport response with no user-facing answer when the output budget is too small.

    Why it happens

    The model can spend the allowed tokens on reasoning before reaching answer content. In the described API path, reasoning and content are separate response fields.

    How to work through it

    1. Inspect content, reasoning and the completion status separately.
    2. Set a budget that leaves room for a final answer on the intended workload.
    3. Treat missing answer content as an incomplete result rather than presenting transport success as an answer.

    The solution

    If content is null while the reasoning field contains text and generation hit its limit, increasing the answer budget may be the relevant correction. Do not discard the distinction by converting every 200 response into success.

    How to check it

    Use a representative reasoning request and check that it produces meaningful final content within the chosen limits. Also exercise the capped case so the caller reports it honestly.

    Where this stops

    Response fields and token accounting vary by provider and model. A larger budget can cost more and does not fix every empty response; inspect the actual completion reason.

  40. AI and agents

    Let code aggregate. Let the model judge.

    LLMs summarise well and count badly. Do the heavy aggregation in code and hand the model only the part that needs judgment.

    How it works

    The problem

    A reporting agent can stall while collecting hundreds of records that a deterministic query could have reduced to a few useful totals.

    Why it happens

    Raw pagination spends context and model effort on counting and grouping. Those operations have exact rules that code can apply before the judgment step.

    How to work through it

    1. Define the summary questions and their data scope.
    2. Compute counts and groups in code or an appropriate server-side aggregation.
    3. Pass compact results and coverage limits to the model for explanation.

    The solution

    For a monthly ticket summary, return counts and a bounded set of themes rather than asking the model to read every ticket and calculate totals. Keep drill-down available for the records behind a claim.

    How to check it

    Compare the aggregate with a checked small dataset and confirm the report’s window, filters and completeness. The model should not describe a partial page as the whole population.

    Where this stops

    The source does not establish a particular time-bucketing implementation. Aggregation still depends on correct queries and access rules, and a compact summary can hide outliers unless the reporting design preserves them.

  41. AI and agents

    Embed the body, not the front matter.

    On small documents the metadata header can be most of the tokens, so the vector ends up describing the header, not the content.

    How it works

    The problem

    A short document’s embedding can describe its metadata more strongly than its useful prose when the header contains long hashes and bookkeeping fields.

    Why it happens

    The embedding sees the tokens it is given. Front matter can dominate a small file even though those fields say little about the subject a reader wants to find.

    How to work through it

    1. Parse the front matter as structured metadata.
    2. Embed the meaningful document body.
    3. Keep identifiers, provenance and filter fields alongside the vector rather than mixing every field into its text input.

    The solution

    A short note with two long digests can spend much of its token budget on identifiers. Removing those fields from the embedding input leaves more of the representation devoted to the note’s actual explanation.

    How to check it

    Inspect the exact text sent to the embedding model and its token composition. Then compare retrieval on representative content questions while confirming that metadata filters still work.

    Where this stops

    Some titles and concise contextual labels improve retrieval. The boundary is deliberate selection of useful context, not a requirement to discard every metadata field or the provenance needed to audit a result.

  42. AI and agents

    Ground in your documents when you can't train on them.

    Most companies can't fine-tune on their own data. Retrieving the right document into context gets most of the benefit.

  43. AI and agents

    Check that the GPU shares add up.

    Each model's memory fraction looked fine on its own; together they asked for 1.67 cards. Validate invariants across files before anything starts.

    How it works

    The problem

    Each model configuration can look valid on its own while all the configured instances together reserve more GPU memory than the card can provide.

    Why it happens

    Splitting configuration across files removes the place where a shared resource limit was previously obvious. Per-file validation cannot establish a cross-file invariant.

    How to work through it

    1. Load every instance assigned to the same GPU.
    2. Sum the relevant memory shares and validate the combined budget.
    3. Reject overcommit before starting any instance and show which allocations caused it.

    The solution

    If three files request fractions that add to 1.67, each fraction can be individually below one while the fleet still asks for more than one card. The validator needs the whole set.

    How to check it

    Use a fixture whose individual files are valid but whose total exceeds the allowed budget. Expect the fleet check to reject it, then check a valid combined allocation.

    Where this stops

    A fraction sum is only one invariant. Model weights, caches, runtime overhead and other processes still need headroom; a valid configuration total does not prove the device is currently free.

  44. AI and agents

    A good playbook is defined by its edges.

    In scope, out of scope, reject when. A skill without boundaries does a little of everything and nothing reliably.

    How it works

    The problem

    A playbook becomes unreliable when it tries to help with every nearby task. Readers cannot tell when to use it or when it should stop.

    Why it happens

    Without explicit boundaries, the same instructions are applied to cases with different prerequisites and risks.

    How to work through it

    1. State the task the playbook owns.
    2. Name the adjacent work it excludes and the conditions that require rejection.
    3. Describe the inputs, expected result and route for a case outside its scope.

    The solution

    For example, a read-only reporting playbook can explain how to collect and summarise data while directing configuration changes to another procedure. That keeps a request for a report from quietly becoming permission to alter the system.

    How to check it

    Try a clear in-scope case, a nearby out-of-scope case and a case missing a prerequisite. Expect useful execution for the first and a clear boundary or handoff for the others.

    Where this stops

    Boundaries need maintenance as tools change. A rejection should explain the missing condition or next route; “out of scope” by itself does not help someone finish the work.

  45. AI and agents

    Check the stored chunk before blaming the model.

    A wrong answer about a PDF table came from corruption at parse time. Swapping the LLM would never have fixed it.

    How it works

    The problem

    A wrong answer about a PDF table can send debugging toward the language model even when the wrong numbers were already stored in retrieval.

    Why it happens

    An index faithfully returns the chunks it receives. If parsing corrupted the table, retrieval and generation can both behave as designed while propagating that error.

    How to work through it

    1. Read the retrieved chunk exactly as stored.
    2. Compare its table values and relationships with the source page.
    3. Fix extraction and rebuild the affected content before changing the answer model.

    The solution

    If a source table says 24 nodes and 9.6 kW but the stored chunk says 12 and 4.8, a more capable model still receives the wrong evidence. The repair belongs upstream at parsing.

    How to check it

    After reprocessing, inspect the stored chunk again and repeat the same question. Check both that the values survived extraction and that retrieval supplied the corrected evidence.

    Where this stops

    Clean chunks do not prove the model is innocent. Once the evidence is correct, continue through ranking, context assembly and answer generation to find any remaining fault.

  46. AI and agents

    Synthetic test documents lie about real ones.

    Two changes passed synthetic tests and broke on a real datasheet, whose PDF outline was flattened to one level. Keep a real-document soak test.

    How it works

    The problem

    A document pipeline can pass tidy synthetic fixtures and still break on an ordinary real document. The missing complexity never entered the test.

    Why it happens

    Real PDFs carry layout and outline structures that small hand-made examples often omit. In the source account, a real datasheet’s outline was flattened to one level.

    How to work through it

    1. Keep fast synthetic cases for isolated behavior.
    2. Add an authorized representative real document to a longer-running check.
    3. Verify the structure that matters, such as nested sections, rather than merely checking that parsing finishes.

    The solution

    For a document pipeline, include a real PDF whose outline is flattened to one level. Check whether the downstream section and table handling still produces useful chunks instead of assuming that the input outline supplies a clean hierarchy.

    How to check it

    Compare the extracted sections and table chunks with a checked reference for that document and rerun after parser or chunker changes. A green extraction command alone does not establish that useful document structure survived.

    Where this stops

    One real document does not represent every format. Choose samples for known risks, keep their use and privacy appropriate, and retain focused fixtures so failures remain easy to diagnose.

  47. AI and agents

    Context length is paid for in KV cache.

    vLLM must fit one full max-length request (length times per-token KV) or it refuses to start. Longer context means fewer requests fit at once.

    How it works

    The problem

    Increasing a server’s advertised context length can prevent startup or reduce the number of requests it can serve at once.

    Why it happens

    The runtime needs cache space for the keys and values associated with input tokens. A larger maximum request consumes more of that fixed memory budget.

    How to work through it

    1. Read the configured maximum context length and the reported cache capacity.
    2. Check that one full-length request fits.
    3. Choose the supported context and concurrency together, then verify the deployed configuration.

    The solution

    For illustration, a cache that holds 32,000 tokens can fit more short requests than requests near a 16,000-token limit. Advertising a limit above the cache’s total capacity cannot be rescued by testing only short prompts.

    How to check it

    Expect startup to report enough capacity for the maximum length. Then exercise representative lengths and inspect admission or memory behavior before treating a concurrency estimate as operational capacity.

    Where this stops

    The arithmetic is a planning model. Model architecture, runtime version and scheduling affect the real capacity, and token capacity should come from the running configuration’s report.

  48. AI and agents

    Pin KV cache in bytes. Let the runtime count tokens.

    A fixed byte budget skips memory profiling and makes the footprint repeatable. How many tokens those bytes hold changes between versions, so re-read it after upgrades.

    How it works

    The problem

    A model server’s memory footprint can change when startup profiling sees a different amount of free GPU memory.

    Why it happens

    A fixed key/value cache budget reserves a stated number of bytes and skips that profiling choice. The token capacity those bytes provide still depends on the runtime and model.

    How to work through it

    1. Derive a safe cache budget with other instances paused.
    2. Pin the cache in bytes and keep the model and context configuration recorded.
    3. After an upgrade, read the runtime’s new token-capacity report before setting concurrency.

    The solution

    Treat bytes as the reservation and tokens as the runtime’s interpretation of it. If an upgrade reports fewer tokens for the same byte budget, update the concurrency expectation rather than assuming the old capacity still holds.

    How to check it

    Inspect startup output and the combined GPU footprint. Confirm that at least one maximum-length request fits and size simultaneous requests from the pinned configuration’s report.

    Where this stops

    Re-derive the budget when the model, context limit, memory share or card changes. A byte pin makes one allocation repeatable; it does not freeze every other source of GPU memory use.

  49. AI and agents

    Make every run report its own spend.

    Cost was visible only for runs that logged their tokens. A run that doesn't report spend is spend you find out about later.

    How it works

    The problem

    A campaign total can look precise while leaving out runs that did not record usage. Automated runs are easy to miss because nobody is watching their individual invoices.

    Why it happens

    The total can only include the usage records that exist. Missing reporting creates unknown spend, not zero spend.

    How to work through it

    1. Give each run a stable identifier and a usage record.
    2. Include automated and delegated work in the same accounting boundary.
    3. Label each amount as billed, estimated or notional, and surface missing records.

    The solution

    Have the run report its own totals when it finishes. A campaign report can then add known amounts and name the runs whose cost is still unknown, instead of silently treating those runs as free.

    How to check it

    Compare the run list with the usage-record list and investigate unmatched entries. Cross-check one representative record against the harness or billing source before trusting a large aggregate.

    Where this stops

    Token counts and billed cost are different quantities. Pricing, caching and incomplete streaming records can affect conversion, so an estimate should keep its label.

  50. AI and agents

    No model as the judge in a gate.

    A deterministic scorer gives the same answer twice. A model judge can flip on a rerun, and a gate that flips isn't a gate.

    How it works

    The problem

    A required gate becomes difficult to trust when the same unchanged answer can pass once and fail on the next run.

    Why it happens

    A language-model judge adds variable interpretation and generation to the scoring step. That variability can obscure whether the candidate itself changed.

    How to work through it

    1. State the properties the gate must establish.
    2. Score machine-checkable properties with deterministic code.
    3. Keep judgment-based review visible as a separate verdict with its evidence and limits.

    The solution

    For a structured output, validate required fields and exact invariants in code. The same input should receive the same structural verdict, leaving a reviewer to judge qualities such as clarity that the schema cannot establish.

    How to check it

    Run the scorer twice on identical fixtures and expect identical results. Add a known invalid fixture and confirm that it is rejected for the intended rule.

    Where this stops

    Deterministic scoring is only as good as its rules. Free-form quality may still need human or bounded model review; a stable score does not prove that every important property was checked.

  51. AI and agents

    Embedding models need KV cache too.

    They generate nothing, but the model still stores keys and values for every input token, so an embedder with too little cache won't start.

    How it works

    The problem

    An embedding server can refuse to start even though it will never generate an output token. Budgeting only for model weights leaves out working memory required by the serving runtime.

    Why it happens

    In vLLM, the embedding deployment still reserves key and value cache for input tokens. Its configured input length must fit the available cache.

    How to work through it

    1. Read the runtime’s startup memory and cache report.
    2. Check the maximum embedding input length against the reported capacity.
    3. Adjust the cache budget or supported input length, then restart and inspect the new report.

    The solution

    Treat the embedder as a separate memory consumer. A small model sharing a card with a chat server can still need enough reserved cache for its longest accepted input.

    How to check it

    Expect startup to succeed with capacity for at least one maximum-length request. Then check an input at the supported length rather than relying only on a short test sentence.

    Where this stops

    This is a serving-runtime requirement, not a claim that every embedding architecture uses persistent generation-style cache. Check the actual backend and version before copying a memory budget.

  52. AI and agents

    Text embeddings miss diagrams.

    Embed page images too, as patch vectors, so charts, tables and layouts that text extraction loses can still be found.

    How it works

    The problem

    A search can find the words on a page and still miss the diagram that explains them. Text extraction drops the positions, shapes and connections that made the page useful.

    Why it happens

    A text vector represents extracted words. A page-image model represents visual patches, so it can preserve signals that never became text.

    How to work through it

    1. Parse the document once, keeping both text chunks and page images.
    2. Embed the text and page images into separate collections, joined by a stable document identifier.
    3. Query both tracks and merge their results with the visual model’s scoring method.

    The solution

    Keep the text path and add a visual path beside it. For example, a block diagram with very little text can then be retrieved as a page rather than disappearing from the searchable content.

    How to check it

    Use a question whose answer depends on a diagram. Check that retrieval returns the right page and that the answer points to the relevant visual evidence.

    Where this stops

    Visual retrieval adds storage and model cost. A retrieved page still needs to be read correctly; finding it does not prove the answer.

Systems and operations

50 lessons

  1. Systems and operations

    Evaluate the installed path, including its managed policy.

    A test harness that injects its own tools may bypass the installation users actually run—or fail before it starts. Read the host policy and make the test exercise the supported entry point.

    How it works

    The problem

    A command-line evaluation can work in a developer checkout and fail on a managed host. The harness supplies its own tool configuration, while the host already has an exclusive managed configuration that controls which servers may start.

    Why it happens

    The harness assumes it owns tool setup. The managed policy assigns that ownership elsewhere. Adding more per-run flags does not resolve the conflict, and a test against a separate checkout may miss faults in the installed revision.

    How to work through it

    1. Inspect the supported managed configuration and determine who owns server selection. Check the installed revision and entry point before starting a paid evaluation.
    2. When managed configuration is authoritative, exercise that installed route. Refuse a missing or mismatched installation before making model calls.
    3. Keep each user’s identity on the supported server path and verify an idempotent installation check. Record the tested revision so later results name the code that actually ran.

    The solution

    Let deployment policy determine the evaluation entry point. The test should verify the same installation boundary users rely on, while keeping its own data and expected results separate.

    How to check it

    The source reports that the managed-install check was repeated without changing the installation. For another host, deliberately supply a missing or wrong revision and confirm the preflight stops before evaluation spend.

    Where this stops

    This concerns the recorded exclusive managed setup, not every tool server or managed policy. A successful installation check is not proof of answer quality, complete isolation or a successful evaluation run.

  2. Systems and operations

    exec inside a pipeline can leave the parent script running.

    The personal statusline appeared, but the default appeared too. The handover test only checked that the personal output existed.

    How it works

    The problem

    A personal statusline was supposed to replace the default. Instead, both printed. The two lines differed in one field, which pointed to two producers rather than one producer repeating its output.

    Why it happens

    The script used a pipeline to feed input into exec. In this Bash execution path, exec replaced the pipeline child. The parent script continued and printed its own statusline afterwards.

    How to work through it

    1. Reproduce the duplicate output with a small personal script whose output is easy to recognise.
    2. Write a failing test that checks the entire output: the personal line appears and the default line does not.
    3. Replace the pipe with exec bash "$user_sl" <<<"$input" so the handover happens in the parent script.

    The solution

    Use a redirect or here-string when exec needs to replace the current script. A replacement contract needs an exclusive-output assertion; checking that the new output appears leaves half the contract untested.

    How to check it

    Two failing tests were added before the change. The fix merged to main. The source also records four full-suite failures reproduced on unchanged main.

    Where this stops

    This correction uses Bash; a here-string adds a newline, so use a suitable input redirect if exact bytes matter. The fix merged, but the privileged deployment remained with the operator and is not established by this record.

  3. Systems and operations

    A job that ends exactly at the time limit wasn't finished.

    Runs stopping at the partition ceiling were hung, not done. Raising the limit to unlimited only let them hang longer.

    How it works

    The problem

    A job that ends at exactly the scheduler's maximum runtime may have been stopped by the limit rather than completed. Raising the ceiling can make the same defect consume more resources for longer.

    Why it happens

    The scheduler enforces elapsed time, not useful progress. A runaway simulation can keep a core busy, while a broken wrapper can hold an allocation after the real tool has finished.

    How to work through it

    1. Compare elapsed time with the configured cap, exit reason, CPU use and tool log progress. Separate a long advancing run from a stuck tool and from an idle allocation.
    2. Set realistic per-stage deadlines from run history and make missing time limits a visible submission problem. Ensure the wrapper releases resources even if its client disappears.

    The solution

    Fix the termination and progress mechanism before extending the limit. A longer allowance is justified by evidence of useful work, not by the fact that the previous run reached its ceiling.

    How to check it

    Use controlled cases for a slow advancing task, a stalled task and a disconnected client. The wrapper should preserve useful work within policy and reclaim resources when progress or ownership is lost.

    Where this stops

    CPU activity alone cannot prove progress. Some legitimate runs are long, and the historical exact-cap pattern is a diagnostic clue rather than proof that every capped job was hung.

  4. Systems and operations

    Retries can cause the outage they're meant to survive.

    A timed-out request is retried while the first attempt is still running downstream. Under load, that doubles traffic exactly when it hurts.

    How it works

    The problem

    A client can time out and retry while the first attempt is still consuming downstream capacity. During overload, the extra attempt adds work precisely when the service has the least room for it.

    Why it happens

    A timeout ends the caller's wait; it does not necessarily cancel the remote operation. Several layers retrying independently can multiply one original request into many overlapping attempts.

    How to work through it

    1. Map where retries occur and set one overall deadline and attempt budget. Retry only transient failures and ensure repeated operations are safe, using an idempotency key where the service supports it.
    2. Add bounded backoff and jitter, and propagate cancellation where it actually works. Limit concurrent attempts so a failing dependency cannot create an unbounded queue of replacement work.

    The solution

    Treat retries as extra load that needs a budget. A visible final failure can be healthier than silently keeping every timed-out operation alive.

    How to check it

    In a controlled delay test, inspect downstream attempts as well as client errors. The number of active operations should remain bounded, and a timeout should not cause repeated side effects.

    Where this stops

    Backoff cannot create capacity, and some servers ignore cancellation. Simulations can expose a risky mechanism but do not establish that production code has survived it; a real integration test is a separate check.

  5. Systems and operations

    Judge a process by its private memory.

    Listings count shared buffers once per process, so a database looks enormous. Private memory shows what each process really holds.

    How it works

    The problem

    Summing process memory listings can make a database look much larger than the memory it uniquely occupies. Stopping the largest-looking process on that basis may target the wrong source of pressure.

    Why it happens

    Resident memory can include pages shared by several processes. Adding those numbers counts shared buffers repeatedly, whereas private memory identifies pages belonging to only one process.

    How to work through it

    1. Separate private and shared memory in the inspection, using the operating system's available accounting tools. Compare process figures with total host pressure, swap and filesystem-backed memory.
    2. Take another sample after an interval to distinguish a growing footprint from stable shared buffers. Check the process role before deciding whether its memory is expected or idle waste.

    The solution

    Use private memory for process attribution and shared-page-aware totals for the host view. Keep scratch files in the investigation too, because RAM-backed files may not be obvious in one process's listing.

    How to check it

    For an illustrative comparison, inspect several connections to the same database. Their listed resident sizes should not be treated as independent private allocations of the shared buffer pool.

    Where this stops

    Private memory alone does not describe every resource a process causes the system to retain. Permissions, kernel accounting and swapped pages can limit what a single snapshot reveals.

  6. Systems and operations

    On a RAM-backed /tmp, scratch files are memory.

    Files in tmpfs count against RAM and can dwarf a process's own footprint. Clean up scratch after every slice.

    How it works

    The problem

    A helper can exit while its temporary files keep consuming a large share of host memory. Looking only at running processes can miss the scratch space left behind.

    Why it happens

    On a tmpfs filesystem, file data uses RAM and may use swap. A directory called /tmp does not necessarily live on disk, and its contents can outlast the process that created them.

    How to work through it

    1. Check the mount type and available space on the actual host before choosing a scratch directory. Give each run an identifiable directory and a bounded expected footprint.
    2. Clean up completed run directories at the workflow boundary, keeping only the artifacts needed for diagnosis. Confirm ownership and retention before removing anything that another process may still need.

    The solution

    Treat RAM-backed scratch as a budgeted shared resource. Fast temporary I/O is useful only while its memory cost leaves room for the rest of the machine.

    How to check it

    Compare scratch usage before a representative run and after cleanup. Expected temporary growth should return near the starting level, with required outputs preserved elsewhere.

    Where this stops

    Hosts differ: /tmp can be disk-backed on one machine and tmpfs on another. RAM scratch also changes persistence and timing behaviour, so disk-specific correctness checks still need a disk path.

  7. Systems and operations

    Missing glyphs look like missing words.

    Blank gaps in the terminal were font coverage, not locale. fc-list :charset=<hex> shows which fonts have the character.

    How it works

    The problem

    Blank gaps in terminal output can look like lost words or a broken locale. First separate a problem identifying the character from a problem drawing a valid character.

    Why it happens

    Encoding determines which character the bytes represent; font coverage and selection determine whether it can be drawn. Readable ordinary text alongside a missing symbol is a reason to investigate rendering, rather than change the locale at random.

    How to work through it

    1. Identify the affected character and check coverage for its code point with fc-list :charset=<hex>. Compare a working character with the missing one; the related code-point lesson explains this probe in more detail.
    2. If coverage is absent, consider a suitable user-local fallback font. If coverage exists, inspect font selection and terminal behaviour before installing more fonts or rewriting locale settings.

    The solution

    Let the evidence choose the layer to investigate. A valid character with no covering font points to a glyph gap; a coverage result by itself still leaves font selection and terminal rendering to check.

    How to check it

    Render a short line containing ordinary text and the affected symbols in the original terminal. After any cache refresh or restart, check both the symbols and neighbouring spacing rather than relying on a font preview.

    Where this stops

    The source records a recommended installation, not a verified fix. This is the same font-coverage case used in the related code-point lesson, viewed as a diagnosis across layers. The two historical log dates do not establish separate observed failures.

  8. Systems and operations

    X has no timeouts.

    One stalled hop in a forwarded display chain can freeze a whole shared desktop. Remove the forwarding; moving users doesn't help.

    How it works

    The problem

    A shared remote desktop can appear frozen even while its server is alive. Restarting that server may destroy unrelated long-running work without identifying what blocked the display.

    Why it happens

    A forwarded GUI depends on a relay chain between the tool and display. A stalled client holding an input grab is a plausible cause of a wider freeze, but that explanation needs a test.

    How to work through it

    1. Inspect the display server, forwarded jobs and resource pressure before restarting anything. A normal display query answering does not by itself rule out an input grab.
    2. Identify the suspect client and arrange a controlled way to disconnect or stop that job. Compare the desktop before and after, keeping other running work intact.

    The solution

    Reduce the shared failure domain by placing an interactive tool in its own job desktop and viewing that desktop remotely. This removes the forwarding chain from the main session.

    How to check it

    The diagnostic expectation is that removing the suspect client clears the freeze. If it does not, keep the cause open and investigate other display or session failures.

    Where this stops

    The original grab explanation is unproven. Per-job desktops still need working node software, access controls and an idle policy; changing login hosts alone does not test the relay hypothesis.

  9. Systems and operations

    A shared home carries more than files.

    With linger on and an NFS home, every batch job started my user services on the compute node too.

    How it works

    The problem

    A batch launch can unexpectedly start a second copy of user services on a compute node.

    Why it happens

    A shared home exposes the same user-unit files on several hosts. When a scheduler prolog enables linger and starts the user manager, enabled units can start wherever that manager runs.

    How to work through it

    1. Inventory enabled services and timers in the shared home.
    2. Confine host-specific units with an explicit host condition.
    3. Guard newly enabled units with the same rule and inspect for existing duplicate processes.

    The solution

    Illustrative: a tunnel intended for one home host can start again on a farm node simply because both hosts read the same enabled unit tree. Host conditions express where that service belongs.

    How to check it

    Check the effective units on an allowed host and a disallowed host. The intended host should run them; the other should skip them without launching duplicate work.

    Where this stops

    This is host placement, not an authorization barrier. A skipped condition is appropriate for optional placement but should not be mistaken for a failed prerequisite guard on a protected service.

  10. Systems and operations

    srun inside a batch script is a step, not a job.

    Only sbatch makes a child job. Count steps as jobs, or miss their peaks, and the accounting skews.

    How it works

    The problem

    A batch allocation can be counted several times if each srun step is treated as another submitted job.

    Why it happens

    Inside an existing allocation, srun launches a step under that job. Step metrics and job-level metrics describe different scopes, so mixing them can duplicate counts or overwrite the job's peak with a partial observation.

    How to work through it

    1. Retain both job identity and step identity.
    2. Define which scope each aggregate counts.
    3. Combine memory evidence without replacing a known peak with a smaller partial step value.

    The solution

    Illustrative: one batch script with three srun steps is one allocation with several steps. Reporting four independent jobs changes the denominator without adding four independent allocations.

    How to check it

    Inspect representative scheduler records and reconcile job counts with step counts separately. Replaying a partial accounting window should not reduce a previously established high-water value.

    Where this stops

    srun outside an allocation can obtain its own allocation, and nested submission can create child jobs. Inspect the actual scheduler relationship rather than classifying by the command name alone.

  11. Systems and operations

    Closing any descriptor drops a POSIX lock.

    fcntl locks belong to the process, so opening and closing the same file anywhere else in your code quietly releases the lock.

  12. Systems and operations

    A oneshot unit takes its background children with it.

    When the service's main process exits, systemd cleans up the unit's processes, so work launched with & silently never finishes.

    How it works

    The problem

    A background task can vanish when the oneshot service that launched it exits.

    Why it happens

    An ampersand changes shell waiting behavior, but it does not give the child an independent service owner. Under the normal unit cleanup behavior, remaining processes can be terminated when the unit stops.

    How to work through it

    1. Keep finite work in the foreground so the service owns its completion.
    2. If the task must outlive the launcher, give it a separate appropriate service unit.
    3. Inspect unit status and task output together.

    The solution

    Illustrative: a launcher that starts a long copy with & may exit successfully before the copy finishes. Running the copy as the unit's tracked work makes completion and failure observable.

    How to check it

    Rehearse with a disposable delayed task. Expect the service to remain responsible until completion and to report a failing task as a failure.

    Where this stops

    Unit type, KillMode and lifecycle settings affect cleanup. Changing those settings to detach work can create orphan-management problems; model ownership deliberately rather than relying on shell backgrounding.

  13. Systems and operations

    One field can mean different things in different states.

    A scheduler's reason code explains why a pending job waits, but on a running job it means something else. Summing it as wait time is wrong.

    How it works

    The problem

    A scheduler reason field can create fictitious wait time if its meaning is assumed to be the same in every state.

    Why it happens

    The field is contextual. A reason attached to a running or terminal job does not necessarily describe time that job spent pending, so summing records without a state rule changes the metric.

    How to work through it

    1. Define which job states count as waiting.
    2. Count intervals only while the job meets that state definition.
    3. Keep transitions and missing observations explicit when deriving duration.

    The solution

    Illustrative: a reason value repeated on a running job should not add another poll interval to pending time simply because the string still exists.

    How to check it

    Inspect a sample job's state timeline alongside the calculated wait. The measured intervals should lie inside its actual pending periods and reconcile with the chosen time window.

    Where this stops

    Periodic observations cannot always locate transition instants exactly. Report the sampling boundary and do not mix scheduler wait with a tool's later runtime licence wait.

  14. Systems and operations

    Don't keep data where the cleaner sweeps.

    systemd-tmpfiles ages out files under /tmp and /var/tmp. A database kept there was deleted, starting with the tables nobody read, so it looked harmless at first.

    How it works

    The problem

    A database stored under a temporary directory can lose old relation files while recently accessed tables keep working.

    Why it happens

    Temporary-file cleanup follows directory policy and file age, not the operator's idea of important data. Cold database files can meet the cleanup rule before frequently touched files do.

    How to work through it

    1. Inspect the cleanup policy for the actual data path.
    2. Move durable data into a location with an explicit persistence contract.
    3. Verify backups and recovery independently of whether the database currently answers queries.

    The solution

    Illustrative: a rarely read table can disappear before a busy table, making the early symptoms look isolated. A healthy query against the busy table does not establish intact storage.

    How to check it

    Confirm the data directory is outside unintended cleanup scope, inspect required relations and rehearse restoration on a separate instance.

    Where this stops

    A keepalive touch timer is a stopgap with its own failure modes. Persistence also depends on filesystem durability, permissions, backup retention and tested recovery, not just the directory name.

  15. Systems and operations

    MaxRSS misses spikes and double-counts shared pages.

    It is sampled every 30 seconds, so short peaks vanish, and multi-process jobs can read up to 2x high. The cgroup high-water mark is the true peak.

    How it works

    The problem

    A memory history can suggest generous headroom while missing the short peak that actually determines whether a job survives.

    Why it happens

    A periodic sampler sees only its sampling instants. Shared-page accounting can also inflate summed process memory, so the reported peak can err in either direction.

    How to work through it

    1. Record the source and interval of each memory metric.
    2. Capture the job's cgroup high-water mark where available.
    3. Keep sampled curves for timing and shape, while using the appropriate peak for sizing.

    The solution

    Illustrative: a ten-second burst between two thirty-second samples can disappear from the curve. A continuous high-water counter can retain that maximum after the burst ends.

    How to check it

    Compare sampled values and the cgroup peak for a representative multiprocess job. Preserve disagreements as different measurement semantics rather than silently overwriting one with the other.

    Where this stops

    A cgroup peak is only as useful as its scope and collection lifetime. A missing capture is unknown; it cannot be reconstructed exactly from a flat sampled curve.

  16. Systems and operations

    Up is not the same as working.

    A collector passed every liveness check while no new data arrived. Check that fresh rows landed, not just that the process answered.

    How it works

    The problem

    A responsive collector can appear healthy while the dataset it owns stops advancing.

    Why it happens

    Liveness checks show that a process answers. They do not prove that polling, parsing and committing new source data still work, so a broken downstream path can remain invisible.

    How to work through it

    1. Keep a separate liveness signal.
    2. Measure the most recent successfully delivered data for each source.
    3. Compare that age with the source's expected cadence and expose a stale state.

    The solution

    Illustrative: an HTTP health route may return promptly while the latest stored sample is hours old. A freshness check makes the missing delivery visible instead of accepting process uptime as evidence.

    How to check it

    Stop delivery in a disposable setup while leaving the process running. Liveness should stay healthy and freshness should move to stale after the configured allowance.

    Where this stops

    A naturally quiet source needs a suitable heartbeat or delivery contract. Do not treat 'no new business event' as a collection failure without considering how that source behaves.

  17. Systems and operations

    In systemd, a failed condition counts as success.

    A false ConditionPathExists skips the unit and reports success, and Before= only sets order. A fail-closed guard needs Requires=.

    How it works

    The problem

    A service can start even though a supposed prerequisite guard never ran successfully.

    Why it happens

    A false ConditionPathExists skips a unit rather than failing it. Before= establishes order only; it does not make another unit depend on successful validation.

    How to work through it

    1. Make the guard perform a real check that exits nonzero on failure.
    2. Make the protected unit require the guard and order itself after that guard.
    3. Exercise the missing-input path before trusting the dependency graph.

    The solution

    Illustrative: a missing policy file should fail an explicit validator. Requiring that validator and ordering startup after it makes the intended gate visible in the unit configuration.

    How to check it

    Start a disposable protected service with valid and missing guard inputs. The valid case should start; a failed validator should prevent protected startup and leave an observable failure.

    Where this stops

    Requires= alone does not turn a skipped condition into a failure. Startup gating also does not continuously enforce a policy after the protected service has already started.

  18. Systems and operations

    grep -q under pipefail can fail on a match.

    grep exits as soon as it matches, the writer gets SIGPIPE, and pipefail reports the whole pipe as failed.

    How it works

    The problem

    A shell check can find the expected text and still make a deployment script stop. That makes a working host look broken.

    Why it happens

    With pipefail enabled, every process contributes to pipeline status. grep -q exits after its first match; a producer still writing can receive SIGPIPE when the reader closes.

    How to work through it

    1. Keep pipefail enabled and inspect each pipeline status.
    2. Replace the early-exit reader with one that consumes the full stream, such as grep -c, and interpret its result deliberately.
    3. Keep a genuine producer error distinct from a missing match.

    The solution

    Illustrative: a long package listing piped to grep -q can fail only when the match appears early. Consuming the whole listing removes that timing-dependent broken pipe without hiding real errors.

    How to check it

    Exercise a matching input, a nonmatching input and a producer failure. Expect three correctly interpreted results, including success for the first case.

    Where this stops

    The failure depends on output size and timing, so a small local example may not reproduce it. Do not fix it by ignoring every nonzero pipeline status.

  19. Systems and operations

    $(tty || echo none) can give you both.

    tty prints 'not a tty' to stdout and still exits non-zero, so you get two strings. It only shows up in non-interactive runs.

    How it works

    The problem

    An audit record expected to contain one terminal identifier can split across lines during automation.

    Why it happens

    tty writes a diagnostic to standard output when no terminal exists, then exits nonzero. A fallback echo adds another value without removing the first output.

    How to work through it

    1. Capture the terminal name only on success.
    2. Suppress or discard tty's unsuccessful standard output before supplying a single fallback.
    3. Exercise the audit writer through a noninteractive invocation as well as a terminal.

    The solution

    Illustrative: assigning the output of tty followed by an echo fallback can capture both 'not a tty' and 'none'. Branching on success gives the field exactly one value.

    How to check it

    Inspect raw records from both invocation modes. Each event should remain one parseable record, and the terminal field should contain either a real terminal name or the agreed missing value.

    Where this stops

    Suppress only the expected terminal probe output. Other audit failures still need to be visible, and a missing terminal must not be confused with a missing event.

  20. Systems and operations

    A bind mount pins the old file.

    Sessions that mounted a file keep seeing that version even after it is replaced on disk. Restart them after an update.

    How it works

    The problem

    A long-running session can keep reading an old policy file after deployment has replaced the file visible to new sessions.

    Why it happens

    A file bind mount can keep referring to the mounted object while an atomic replacement changes what the original pathname resolves to. Updating the pathname does not necessarily replace that existing mount.

    How to work through it

    1. After changing a mounted policy, inspect the content from inside an existing session as well as through the deployment path.
    2. Recreate the affected sessions or their mounts through the supported lifecycle, then check the content visible inside the new session.

    The solution

    Treat session refresh as part of deployment when file replacement changes policy. A current host-side file is not proof that every already-running session sees the current bytes.

    How to check it

    In a disposable setup, compare digests before replacement, after replacement in the old session, and in a fresh session. The final session should read the intended deployed version.

    Where this stops

    An in-place write differs from replacing a file object, and a process may cache configuration separately. Check both mount identity and application reload behaviour before choosing the refresh operation.

  21. Systems and operations

    Govern the config, not the install.

    You don't have to win the argument about how people install a tool if a managed policy file applies to every copy on the host.

    How it works

    The problem

    A host can contain several installations of the same tool, making a rule tied to one executable path incomplete.

    Why it happens

    Installation method and policy scope are separate choices. If supported clients read a higher-precedence managed configuration, that shared boundary can govern their behaviour without controlling every package manager.

    How to work through it

    1. Identify the documented managed-policy path and precedence for the client versions you support.
    2. Deploy the protected configuration there, then test each installation route, including editor-bundled clients, against a policy action that should be denied.

    The solution

    Keep one supported host policy and a minimum-version rule where needed. Record which clients actually consume it rather than assuming a shared filename makes all binaries compliant.

    How to check it

    For each supported client, inspect effective settings and attempt a harmless denied action in a test environment. Expect the same denial and useful diagnostics across installation methods.

    Where this stops

    This is governance for clients that honour the policy. Unsupported versions, bypass modes or users who can alter the enforcement layer need separate controls; a configuration file alone is not a hostile-user boundary.

  22. Systems and operations

    A control that can't work is worse than none.

    BlockIOWeight on RHEL 8 needs the CFQ scheduler, which was removed, so it reads as protection while enforcing nothing. Reading the value back proves acceptance, not effect.

    How it works

    The problem

    A resource limit can appear in a service manager’s configuration while doing nothing on the host where it was deployed.

    Why it happens

    The manager accepting a property does not establish kernel support, the active scheduler or that the constrained process sits in the intended group.

    How to work through it

    1. Check the host’s cgroup version, controller availability and scheduler support before selecting a resource-control setting.
    2. After deployment, confirm the target process’s scope and use a bounded disposable workload to observe whether the intended control changes behaviour.

    The solution

    For a legacy I/O weight setting, inspect the scheduler it depends on instead of treating `systemctl show` as proof of protection. Remove or replace a control that cannot take effect on the supported host.

    How to check it

    Read-back should match the intended configuration, and the behavioural check should demonstrate the specific resource effect. Record those as separate observations.

    Where this stops

    The source’s RHEL 8/cgroup v1 finding is version-specific. It does not establish that every CPU or I/O controller is ineffective everywhere, and a behavioural test needs a safe scope on shared machines.

  23. Systems and operations

    A pipeline ending in tail always exits 0.

    Pipe a failing check into tail and the gate goes green. Use set -o pipefail, or don't pipe the gate.

    How it works

    The problem

    A failing check can print an error while the shell reports success after its output is piped through a display command.

    Why it happens

    Without pipefail, a shell normally returns the last command’s status for a pipeline. A successful tail can therefore hide a failed command before it.

    How to work through it

    1. For a Bash gate, enable `set -o pipefail` in the shell that actually runs the pipeline, or avoid piping the gate command.
    2. Keep log trimming separate from the command’s captured status when the surrounding runner needs precise failure reporting.

    The solution

    As an illustrative check, `set -o pipefail; false | tail -n 5` should return non-zero. The failure has no output, which makes the exit status the essential signal.

    How to check it

    Test a failing producer and a successful producer through the real wrapper. Expect failure to remain non-zero after logging and success to remain successful.

    Where this stops

    The unchanged title’s “always” is too broad: tail can fail too. Early-closing consumers can also cause SIGPIPE under pipefail, so inspect expected pipeline behaviour rather than blindly enabling it.

  24. Systems and operations

    Use ProxyJump, not nested remotes.

    Remote editors open one remote per window. To reach a host behind a jump box, put ProxyJump in your SSH config.

    How it works

    The problem

    A remote editor connected to a jump host may not support opening a second remote session inside the same window to reach the final machine.

    Why it happens

    The editor’s remote environment is attached to one target. Nesting another editor transport inside it confuses that model, while SSH already has a way to route through an intermediate host.

    How to work through it

    1. Create an SSH host entry for the destination with its real HostName and a ProxyJump pointing to the approved jump-host entry.
    2. Test the destination alias with SSH locally, then ask the editor’s remote extension to open that same alias.

    The solution

    For example, a destination entry can contain `HostName target.example` and `ProxyJump jump`. The editor connects to the destination, while SSH handles the intermediate hop.

    How to check it

    On connection, confirm the terminal hostname and opened filesystem belong to the final target. Verify host keys for both hops and test file access needed by the project.

    Where this stops

    The example names are placeholders. ProxyJump still requires network reachability, credentials and server policy at both hops; it does not make files mounted only on another host appear on the target.

  25. Systems and operations

    Your shell expands the glob before sudo runs.

    `sudo cat /root-only/*.log` fails because your own user can't list that directory. Wrap the command in `sudo sh -c`.

  26. Systems and operations

    Baked images turn age into version drift.

    A machine built from an old image runs old pins forever. In a baked-image world, how old a host is decides which version it runs.

    How it works

    The problem

    A fleet can be built from one standard image process and still run different software versions because its machines were launched from different image generations.

    Why it happens

    An image captures installed packages and pins at bake time. Without an update or replacement policy, older instances retain those captured choices even when a newer image exists.

    How to work through it

    1. Record the image version and important package versions with each instance, then inventory the running fleet.
    2. Set a rebuild and rollout cadence, including how existing instances move to the new image rather than only changing future launches.

    The solution

    Treat the image as a versioned release. When a pin changes, decide whether existing machines are replaced or updated and keep that choice observable.

    How to check it

    Compare a newly launched instance with an older one against the desired release. Check the actual binary versions as well as image labels, because later in-place changes can separate the two.

    Where this stops

    Instance age is a warning signal, not a version identifier. Patching or replacement can break the age/version relationship, and rollout timing depends on service availability needs.

  27. Systems and operations

    Running the deployment finds what reading it won't.

    A health check read the local user file while real accounts lived in the directory service, so it audited almost nobody. Only the live run showed it.

    How it works

    The problem

    A deployment health check can run cleanly while inspecting only a small local subset of the accounts the host actually uses.

    Why it happens

    Reading a local user file assumes that identity is local. On a directory-backed host, that assumption can omit the people whose setup the deployment is meant to verify.

    How to work through it

    1. State the host assumptions and use the system’s supported identity lookup rather than treating the local file as the whole population.
    2. Run the deployment and its health check on a representative host, comparing discovered accounts and paths with the intended scope.

    The solution

    Include a directory-backed account in the acceptance case. A script that audits local accounts successfully still needs to demonstrate that the real identity source reaches the audit.

    How to check it

    Check which accounts were examined, not only the exit code. Exercise a known bad setup for an in-scope directory account and expect the health check to report it.

    Where this stops

    A live run covers that host and version, not an entire fleet. Keep differences such as directory services, home-directory links and security policy explicit when expanding deployment.

  28. Systems and operations

    An idle service in swap isn't a leak.

    A long-lived model server with nothing to do gets its weights paged out. It looks like a leak and is usually just the kernel tidying.

    How it works

    The problem

    A long-lived model service can show substantial swap usage after sitting idle. That number alone is not enough to diagnose a memory leak.

    Why it happens

    The kernel can move infrequently used pages out of RAM. Model weights remain part of the process even when those pages are not resident, so swap records placement rather than proving continued allocation.

    How to work through it

    1. Observe the process’s resident memory and swap over time, together with its workload and idle periods.
    2. Compare repeated equivalent requests and subsequent idle periods; look for continuing growth rather than judging one snapshot.

    The solution

    Separate retained model data from unbounded growth. If an idle service is unnecessary, an idle timeout may address resource use without calling normal paging a leak.

    How to check it

    The useful evidence is a time series: does memory settle after comparable work, or keep growing? Include response latency after idle, because paging back in can still affect users.

    Where this stops

    Swapping can coexist with a real leak or host pressure. A stable footprint does not establish acceptable performance, and a single observation cannot settle either question.

  29. Systems and operations

    Initialise what your cleanup trap uses.

    Under set -u, a trap that reads an unset variable fails itself, and the cleanup never runs.

    How it works

    The problem

    A shell script can fail before starting a background process, then fail again in its cleanup handler and leave other resources behind.

    Why it happens

    With set -u, expanding an unset variable is an error. A trap that assumes a process id has already been assigned is unsafe on earlier exit paths.

    How to work through it

    1. Before registering the trap, initialise every variable it reads, using an empty value for a resource that does not yet exist.
    2. Guard each cleanup action with a check that the resource was created; update its variable immediately after creation.

    The solution

    For example, initialise tpid before installing a trap and only signal it when it contains the tunnel process id. Make partial setup a normal case the cleanup code handles.

    How to check it

    Force an exit before resource creation and another after it in a disposable run. Both should preserve the original failure while avoiding an unbound-variable error; the later case should remove the created resource.

    Where this stops

    Initialisation prevents this failure, but cleanup can still fail for other reasons. Avoid signalling an unrelated process and preserve useful diagnostics from the original error.

  30. Systems and operations

    inotify doesn't see another machine's writes.

    On NFS, a file changed from another client fires no event. Watching across hosts needs a poller.

    How it works

    The problem

    A watcher can react immediately to local edits and still miss a configuration file changed by someone on another machine.

    Why it happens

    inotify reports events through the local kernel. A write made through another NFS client need not generate the local event on which the watcher relies.

    How to work through it

    1. Keep the local event watcher for quick feedback, but add a bounded polling interval for shared files.
    2. On each poll compare the relevant content or version, and send changes through the same validation and apply path as local events.

    The solution

    Use events for speed and polling for eventual detection across hosts. Record the maximum expected delay so a quiet watcher is not mistaken for proof that nothing changed.

    How to check it

    On a disposable shared file, edit from a second client and expect detection within the polling bound. Also check that an unchanged poll does not reapply the configuration.

    Where this stops

    Polling adds delay and filesystem work. NFS caching may affect visibility, so measure the supported arrangement rather than promising instant cross-host notification.

  31. Systems and operations

    On a shared host, look for the farm first.

    Size the job and send heavy work to the scheduler. With no farm, run gently. Only on your own machine do you just run it.

    How it works

    The problem

    A long test or data job can make everyone else on a shared machine wait. The command may be correct while the place it runs is the wrong choice.

    Why it happens

    A shared login or desktop host has other users whose work is invisible to the current shell. Starting all available workers treats shared capacity as if it belonged to one task.

    How to work through it

    1. Estimate the job from previous runs: CPU, peak memory and duration. When there is no previous run, start with a bounded sample rather than assuming the largest allocation is needed.
    2. Check whether the machine is personal or shared and whether a scheduler is available. Apply the configured resource policy to choose a local run or a submitted job.
    3. Request the measured resources when submitting. With no scheduler on a shared host, bound the workers and lower priority where appropriate, then observe the effect.

    The solution

    Make placement part of starting the job. A short note saying where it will run and why is easier to review than a slow host after the fact.

    How to check it

    Check the actual job allocation and peak use after the run. Use that evidence to size the next run instead of repeating the original guess.

    Where this stops

    A lower process priority does not limit memory, and an idle-looking host may still be shared. Local convenience is not evidence that a heavy job belongs there.

  32. Systems and operations

    dirname $0 breaks under a symlink.

    It returns the link's folder, not the script's. Use readlink -f to find where the script really lives.

  33. Systems and operations

    RLIMIT_AS counts reserved memory, not used memory.

    Node, Java and PyTorch reserve big address ranges up front, so a tight cap kills them before they use anything.

    How it works

    The problem

    A program can fail under a memory limit before its resident memory becomes large.

    Why it happens

    RLIMIT_AS constrains virtual address space, including reserved mappings. Runtimes such as Node, Java and PyTorch may reserve large ranges that do not yet occupy equivalent physical memory.

    How to work through it

    1. Distinguish virtual size, resident memory and the limit being applied.
    2. Check the runtime’s allocation strategy before selecting a cap.
    3. Prefer a supported process-group or cgroup policy when the goal is to bound actual memory consumption across workers.

    The solution

    For example, a large reserved heap can exceed an address-space cap while resident use remains modest. Raising or lowering that cap without inspecting the mapping confuses reservation with consumption.

    How to check it

    Run a representative bounded fixture and inspect both virtual and resident measurements. Confirm what happens to child processes and any memory-backed temporary storage under the chosen enforcement.

    Where this stops

    Cgroup accounting and runtime heap settings also have specific semantics. The cited source leaves cluster enforcement unverified, so this draft does not claim a tested alternative cap on that environment.

  34. Systems and operations

    /health can be answered by the wrong server.

    An unrelated service holding the port returns OK. Check /v1/models for the expected model name, then send a real completion.

    How it works

    The problem

    A deployment probe can report healthy because something answers on the port, even when the intended model server never started.

    Why it happens

    A generic health endpoint identifies availability only as precisely as its response contract. Another service can own the same port and return an equally plausible OK.

    How to work through it

    1. Check the process and listening port, then query the service’s identity-bearing endpoint.
    2. Confirm that the returned model list contains the expected served name.
    3. Send a small representative inference request and inspect the returned content.

    The solution

    For example, /v1/models provides more evidence about a model-serving endpoint than /health alone. A completion request adds another layer by exercising actual inference rather than just metadata routing.

    How to check it

    Test the probe against a wrong service and against a server with the wrong model alias. Both should fail before the deployment is called ready.

    Where this stops

    An expected alias can still point to incorrect weights, and one completion proves only a narrow path. Keep checks proportional to the required deployment guarantee, and do not treat a CPU rehearsal as GPU validation.

  35. Systems and operations

    Group membership doesn't reach an open shell.

    Add yourself to a group and existing sessions still lack it. Log in again, or use newgrp.

    How it works

    The problem

    A user can be listed in a new Unix group while a shell they already had open still gets permission denied.

    Why it happens

    A process carries a set of group credentials inherited at launch. Updating the account database does not normally rewrite those credentials in existing processes and their children.

    How to work through it

    1. Check the account’s configured membership and the current shell’s effective groups separately.
    2. Start a fresh login session after the group change, or use an appropriate newgrp session.
    3. Retry the intended operation from that refreshed context.

    The solution

    For example, a newly opened login may read a group-protected file while an old terminal cannot. Changing file permissions again would treat the symptom without addressing the stale process credentials.

    How to check it

    Compare the effective group list in the old and new sessions, then perform the actual allowed operation. Check the GID rather than trusting a similar group name.

    Where this stops

    Directory-service caches and host-local group configuration can add delays or differences. newgrp starts a changed shell context; it does not retroactively update every existing application or service.

  36. Systems and operations

    You can't grow a running Slurm job's memory.

    The cgroup limit is fixed when the step launches, so even an admin edit after start doesn't change what is enforced. Get the request right before submitting.

    How it works

    The problem

    A running job can approach its memory ceiling while an administrative request change gives the false impression that more memory has become available.

    Why it happens

    The scheduler installs the enforced resource limit when the job step starts. In the environment described by the source, changing the request after launch did not resize that running cgroup.

    How to work through it

    1. Estimate and request memory before submission using representative peak measurements.
    2. Check the effective limit on the running step rather than only the scheduler’s requested value.
    3. If more memory is needed, use a supported restart or requeue plan with checkpoints.

    The solution

    For example, a long EDA flow can save a stage-boundary database and resume a later stage in a newly sized allocation. A mid-stage memory peak without a checkpoint may require repeating that stage.

    How to check it

    On an authorized test job, compare the submitted request with the effective cgroup limit. Verify that the chosen restart path restores the expected state before relying on it.

    Where this stops

    This reflects the cited cluster behavior, not every scheduler version or administrative configuration. Accounting samples can miss short memory peaks, and a pending request change is not proof of effective enforcement.

  37. Systems and operations

    A desktop build is the wrong artifact for a headless host.

    Remote editing needs only a small server on the host; the UI stays on your own machine.

    How it works

    The problem

    Installing a full desktop editor on a headless host can fail on graphical libraries the remote editing workflow never needed.

    Why it happens

    A remote development setup separates the interface from the execution host. The local machine runs the UI, while a smaller server on the remote host provides files, terminals and language services.

    How to work through it

    1. Identify whether the task needs a remote server, a browser service or a full desktop application.
    2. Choose the supported server package for the host’s architecture and runtime libraries.
    3. Keep the UI on the client machine and connect through the approved transport.

    The solution

    For example, a headless build host can run an editor’s remote server without opening a graphical session. That avoids introducing a desktop stack just to edit files and run tools near the source.

    How to check it

    Connect from the intended client, open a fixture file and run a small remote command. Verify where extensions and processes execute rather than assuming everything stays local.

    Where this stops

    Remote servers still have operating-system and library requirements. This is deployment guidance, not a claim that every desktop package is wrong or that the legacy installation was reproduced.

  38. Systems and operations

    One command-line argument is capped at 128 KiB.

    Linux refuses a single argv element over 131,072 bytes, so a big prompt passed as an argument has a hard ceiling. Use stdin or a file.

    How it works

    The problem

    A large prompt can fail before the target program starts when it is placed in one command-line argument.

    Why it happens

    Linux limits individual argument strings as well as the combined argument and environment space. On common 4 KiB-page systems, the per-string ceiling is about 128 KiB, including its terminator.

    How to work through it

    1. Check whether the CLI accepts stdin or a prompt file.
    2. Use that supported input path for large content rather than constructing one huge argv element.
    3. Keep environment and total invocation size bounded too.

    The solution

    For example, a prompt assembled from several documents can cross the limit even though the model accepts a much larger context. Moving the bytes to stdin addresses the launch boundary, not the model’s own input limit.

    How to check it

    Try a controlled fixture around the expected boundary, then confirm the program receives the complete stdin or file content. Report launch failure separately from model rejection.

    Where this stops

    The exact per-string limit depends on platform and page size; 131,072 bytes is not universal. File permissions and retention also matter when prompts contain sensitive content.

  39. Systems and operations

    A running daemon keeps using its deleted binary.

    Long-running processes execute the unlinked old file until they restart, so an upgrade isn't live just because the file changed.

    How it works

    The problem

    Installing a new executable does not prove that a long-running service is executing the new version.

    Why it happens

    A running process retains its loaded code and mappings. Replacing the file used for the next launch does not generally replace code already loaded into an existing daemon.

    How to work through it

    1. Check the installed version and the active process separately.
    2. Use the service’s supported restart or reload procedure after the upgrade.
    3. Inspect the restarted process and exercise the behavior the upgrade was meant to change.

    The solution

    For example, a versioned launcher can point at a new build while an old background session keeps running. A fresh shell proves the launcher’s choice, but says little about that existing session.

    How to check it

    Confirm a new process start and obtain version information from the active service when possible. Follow with a small request that reaches the running instance.

    Where this stops

    Some services explicitly support live reload or re-exec, and interpreted programs may load files later. Use the application’s update contract rather than assuming every replaced file behaves identically.

  40. Systems and operations

    Restart=always can't see a wedged model.

    systemd only reacts to process exit, so a hung server needs a probe. A model that runs out of GPU memory minutes after start never trips the start limit and restarts forever.

    How it works

    The problem

    A supervisor can keep reporting a live process while the server no longer answers useful requests.

    Why it happens

    Restart policies react to exits. A hung process has not exited, and a process that fails only after the start-limit window may restart indefinitely without crossing that short-window threshold.

    How to work through it

    1. Probe useful service behavior with a timeout, not just process existence.
    2. Choose a restart budget and observation window that cover delayed failures.
    3. Record repeated failure reasons so memory exhaustion is not hidden by automatic recovery.

    The solution

    For example, a model server may start successfully and run out of GPU memory later. A bounded request probe can expose that failure; a startup-only check cannot. Recovery should include a limit or escalation path.

    How to check it

    Rehearse a nonresponsive fixture and a delayed-exit fixture. Confirm that each becomes unhealthy and that the recovery policy eventually stops or escalates repeated failures.

    Where this stops

    Probe failure can also reflect network or dependency problems, so avoid restarting blindly on every timeout. CPU-only control tests do not verify real GPU memory behavior.

  41. Systems and operations

    No answer from nvidia-smi means unknown, not free.

    A hung driver or a missing tool says nothing about the card. Treating silence as free lets you start work on a busy GPU.

    How it works

    The problem

    A launcher can overcommit a GPU if it treats a failed utilization query as an empty card.

    Why it happens

    A missing tool, hung driver or timed-out command provides no measurement. Converting that absence to zero turns an observation failure into permission to start work.

    How to work through it

    1. Give the device query a bounded execution time.
    2. Represent valid readings and unavailable readings separately.
    3. Start new work only after the required resource checks return usable evidence.

    The solution

    If nvidia-smi times out, report GPU state as unknown and stop the admission decision. Do not fill in zero used memory simply because no number was parsed.

    How to check it

    In a safe fixture, simulate a missing executable, timeout and malformed response. Expect each to remain unknown, while a valid idle reading follows the normal admission path.

    Where this stops

    A fresh reading still is not a reservation. Another process can allocate memory after the check, so concurrent launchers need coordination in addition to honest state reporting.

  42. Systems and operations

    The man page said order didn't matter. It did.

    An experiment showed the first listed partition wins when both have room. Test scheduler behaviour; don't just read it.

    How it works

    The problem

    A scheduler’s documented behavior can differ from what an installed configuration actually does. Routing work based only on the manual can put jobs in the wrong partition.

    Why it happens

    The source records a controlled case where the first listed eligible partition won when both had room, despite the manual’s statement that order was ignored.

    How to work through it

    1. Inspect the live scheduler configuration and submission wrapper.
    2. Submit small equivalent jobs with the partition list in each order.
    3. Use accounting records to compare the partition actually chosen.

    The solution

    For an illustrative pair, submit to interactive,standard and then standard,interactive under comparable availability. Changing only the list order makes the routing result easier to interpret.

    How to check it

    Check the recorded partition and resource conditions for each job. Repeat enough to separate list-order behavior from one partition simply lacking capacity at the time.

    Where this stops

    This is evidence about the tested environment, not a universal Slurm scheduling guarantee. Recheck after priority, configuration or version changes before relying on the same routing rule.

  43. Systems and operations

    MemoryHigh throttles. MemoryLimit kills.

    On cgroup v2, MemoryHigh slows a service down. The older v1 MemoryLimit is a hard OOM kill. Know which one your host has.

    How it works

    The problem

    A memory-control setting can kill work when the operator intended only to slow it down, or have little effect because it was copied to the wrong cgroup version.

    Why it happens

    MemoryHigh on cgroup v2 is a reclaim and throttling threshold. MemoryMax is the hard cap; the older MemoryLimit setting serves the hard-limit role on cgroup v1.

    How to work through it

    1. Identify the host’s cgroup version and supported systemd controls.
    2. Choose soft pressure and hard-stop limits according to the intended behavior.
    3. Inspect the effective unit settings and monitor how the service behaves under controlled pressure.

    The solution

    For a shared service, a soft ceiling can reduce its impact before a hard backstop is reached. Copying a v1 hard-limit directive as if it were a v2 throttle changes that operational contract.

    How to check it

    Use a safe bounded test workload and inspect reclaim, throttling and termination signals. A setting present in a file does not establish that the running host enforces it.

    Where this stops

    A soft threshold is not an absolute memory ceiling. A throttled service can look hung, and limits do not repair a leak; pair them with health checks and investigation.

  44. Systems and operations

    Metrics say how much, never who.

    Token metrics show trend by model but carry no caller. Per-person spend needs the invocation log or a per-key gateway.

    How it works

    The problem

    A model-usage chart can show the total token trend while failing to answer which person made the calls.

    Why it happens

    The Bedrock metric dimensions in this setup identify model-level usage, not the caller needed for per-person attribution. Aggregation has already discarded that distinction.

    How to work through it

    1. Use metrics for overall trends and operational signals.
    2. Use invocation metadata or an identity-aware gateway for caller-level records.
    3. Keep the attribution source and any pricing conversion visible in reports.

    The solution

    If a daily chart rises, it can tell an operator that usage increased. It cannot divide the increase between two people unless another record preserves their identities and relevant usage.

    How to check it

    Trace one controlled request from the caller to its invocation record and then to the aggregate metric. Confirm which fields identify the person and which are merely client-supplied labels.

    Where this stops

    The title refers to this metric setup, not every possible custom metric system. Assumed roles, missing identity fields and price-table assumptions can all limit a per-person cost report.

  45. Systems and operations

    Systemd is enough for a handful of GPU boxes.

    Kubernetes pays off at fleet scale. For a few servers, one unit template and one config file per model are simpler to run and to explain.

    How it works

    The problem

    A small model-serving setup can acquire more orchestration machinery than the machines themselves need. That adds another system for the team to understand and maintain.

    Why it happens

    For a small fixed deployment, process lifecycle and per-model configuration may be the main requirements. systemd already provides those host-level building blocks.

    How to work through it

    1. Use one service template for the serving process.
    2. Give each model its own configuration and template instance.
    3. Group the instances under a target and validate shared GPU limits before startup.

    The solution

    Run two models as two instances of the same unit instead of placing several ExecStart commands in a simple service. Each instance can restart independently while the target represents the group.

    How to check it

    Check start, stop, restart and reboot behavior for the actual host arrangement. Also check the combined memory budget; valid individual files can still overcommit one card.

    Where this stops

    The source’s concrete judgment concerns a very small GPU setup. Multi-host scheduling, elastic placement or stronger fleet requirements may justify Kubernetes or another orchestrator.

  46. Systems and operations

    Ctrl+Z can leave your terminal in mouse mode.

    Suspend a program that turned mouse tracking on, and the shell receives escape codes every time you click. A terminal reset fixes it.

    How it works

    The problem

    Clicking or moving the mouse in a shell can print escape sequences or shift the display after a full-screen terminal program was suspended.

    Why it happens

    A program can enable terminal mouse reporting and then stop before restoring the terminal mode. The shell receives those input codes even though it does not interpret them as mouse events.

    How to work through it

    1. Check whether a full-screen program was suspended rather than exited.
    2. Restore the affected terminal’s modes with an appropriate terminal reset.
    3. Resume or exit the program cleanly when that is safe for the session.

    The solution

    Repair the terminal state before assuming the shell or remote session is corrupt. The important distinction is between the program’s process state and the input modes it left behind.

    How to check it

    After resetting, click and move the mouse in the same terminal and check that no mouse-reporting codes reach the prompt. This is an expected check, not a confirmed outcome of the historical diagnosis.

    Where this stops

    The source records a hypothesis and a reset attempt without confirmation. Mouse-mode leakage is one explanation; a persistent problem still needs investigation of the terminal and running programs.

  47. Systems and operations

    Alert on waste, not efficiency.

    A tiny job at 10% efficiency wastes nothing worth a ping. Gigabyte-hours wasted is what costs money, so alert on that.

    How it works

    The problem

    A tiny job can have terrible memory efficiency and still waste very little capacity. Alerting on the percentage alone makes it compete with genuinely costly jobs.

    Why it happens

    Efficiency is a ratio. The cost of unused reserved memory also depends on how much memory was held and for how long.

    How to work through it

    1. Estimate unused held memory over the job’s duration.
    2. Rank or gate alerts by wasted gigabyte-hours.
    3. Use efficiency as supporting context and explain the sizing issue in a clear sentence.

    The solution

    For illustration, leaving 0.9 GB unused for an hour wastes 0.9 GB·h. Leaving 300 GB unused for the same hour wastes 300 GB·h. Both may look poor as percentages, but their capacity impact is very different.

    How to check it

    Check the units and the held-memory definition, then compare representative small and large jobs. The larger resource loss should rise in priority even if its efficiency percentage looks less dramatic.

    Where this stops

    Peak usage, grouping and shared allocations can make estimates approximate. Keep those limits visible instead of presenting a precise-looking total as an exact bill.

  48. Systems and operations

    Turn off GPU compositing on forwarded displays.

    Chromium's GPU process can crash under ssh -X, VNC or xvfb and black out the whole X session. Disable hardware acceleration there.

    How it works

    The problem

    An Electron app can black out a remote desktop or hang during startup even though its UI code works on a local display.

    Why it happens

    Chromium’s GPU compositing process can fail on forwarded or virtual X displays. The rendering environment is different from a normal local GPU-backed session.

    How to work through it

    1. Identify whether the app is running through SSH X forwarding, VNC or a virtual display.
    2. Disable hardware acceleration in the Electron shell before startup.
    3. Launch again in the same remote environment and inspect both the window and the session.

    The solution

    Use software rendering as the safe default for the affected remote-display setup. This puts the rendering choice in the desktop shell, where it belongs, rather than changing unrelated page code.

    How to check it

    Expect the window to render and startup to complete without taking down the X session. A local-machine launch alone does not check this failure mode.

    Where this stops

    Software rendering may cost more CPU and reduce graphical performance. Keep the setting tied to the supported environment, and investigate separately if the same failure occurs without GPU compositing.

  49. Systems and operations

    Read the output, not the task status.

    A background run reported success because its wrapper exited cleanly. The file it was meant to write said otherwise.

    How it works

    The problem

    A background task can be marked complete even though the command it wrapped failed. Reading only the task status turns a scheduling result into a false success claim.

    Why it happens

    The wrapper reports that it finished managing the task. Its status may not preserve the child command’s real exit code or the contents of the result file.

    How to work through it

    1. Locate the run’s actual output record.
    2. Read the real exit code and the produced artifact.
    3. Report completion only at the level those records establish.

    The solution

    If a wrapper exits cleanly but the verification file records exit 1, the verification failed. An echoed command or a status field containing the word “success” does not override that record.

    How to check it

    Compare the wrapper status, child exit and artifact for a deliberately failing command in a safe fixture. Expect the reporting path to preserve the failure rather than promote wrapper completion to a pass.

    Where this stops

    An output file can also be stale or belong to another run. Match it to the current run identifier and inputs before treating it as evidence.

  50. Systems and operations

    A late cap costs money. A wrong cap takes production down.

    Lateness is bounded, visible and recoverable. A misconfigured automatic stop cuts people off with no human in the loop and no explanation.

    How it works

    The problem

    A budget action can stop production because a filter or threshold is wrong. The affected users may only see access disappear.

    Why it happens

    The failure costs are uneven: a late cap permits extra spend, while a wrong automatic deny interrupts service. Delayed cost data makes an automatic decision less current than it appears.

    How to work through it

    1. Identify the cost of a late action and the cost of a false stop.
    2. Check the budget scope and the principal that an action would deny.
    3. For this risk balance, notify an operator and require review before attaching the deny.

    The solution

    Use the alert as a decision point. The operator can inspect whether the right tier actually exceeded its budget before changing access, rather than letting a mis-scoped total stop unrelated work.

    How to check it

    Check the proposed target and budget filter with a safe, controlled case. Verify that an alert alone does not attach a production deny and that an approved action has a documented reversal.

    Where this stops

    Manual review can be too slow for a strict financial ceiling. This is a trade-off for the stated service risk, not a universal ban on automatic caps.

Security and networking

53 lessons

  1. Security and networking

    List what an agent inherits, not just what it may do.

    Passphraseless SSH keys, plaintext git credentials, cloud profiles and tokens left in settings files all come along with the user's session.

    How it works

    The problem

    An agent run under a user's account can reach more authority than the task prompt mentions. Existing credentials may let a routine helper authenticate to services without any new key being supplied.

    Why it happens

    The operating-system identity carries access to files, sockets, agent sessions and configured credential providers. A passphraseless key or plaintext token can therefore become an available capability by inheritance.

    How to work through it

    1. Inventory credential channels by type and scope without printing their values. Include SSH access, Git credential helpers, cloud profiles, active sessions and tokens embedded in settings files.
    2. Decide which capabilities the task needs and restrict the execution identity or credential delivery accordingly. Remove secrets from broad configuration and rotate any credential whose exposure is established.

    The solution

    Review what the process can actually use, rather than only the permissions described in the agent interface. Keep the inventory current when account setup or execution hosts change.

    How to check it

    Use harmless test identities to verify an allowed operation and a denied one. The expected boundary is that the task works with its intended access while unrelated service authority remains unavailable.

    Where this stops

    An environment allowlist alone does not remove readable credential files or live agent sockets. Encryption at rest also offers limited separation when an authenticated session has already unlocked the credential.

  2. Security and networking

    Let the firewall own the network boundary.

    An allowed-domains list inside a sandbox drifts and is hard to audit. The proxy already sees every connection.

    How it works

    The problem

    A list of blocked download commands does not control where an agent can send data. The same network request may be made through a library, another executable or a child process.

    Why it happens

    Tool rules see particular invocations. A firewall or mandatory proxy sees traffic at a lower boundary, so policy can apply regardless of which program initiated the connection.

    How to work through it

    1. Map the required destinations and protocols, including non-HTTP services that ordinary tools need. Place the policy at an enforced network boundary and make its ownership clear.
    2. Ensure workloads cannot simply bypass the proxy through a direct route. Log allowed and denied destinations at that boundary, then keep tool-level restrictions as additional guards.

    The solution

    Give one network control responsibility for outbound access. This reduces the chance that several partial domain lists drift while each appears to cover the whole system.

    How to check it

    From a controlled test workload, try an allowed destination and a harmless disallowed one through more than one client. The policy should hold across those execution paths.

    Where this stops

    A proxy controls only traffic forced through it, and domain rules can be difficult for raw TCP services. The historical egress control was still a prerequisite, not a completed protection.

  3. Security and networking

    Model output in a spreadsheet can run as a formula.

    Cells starting with = + - or @ execute in Excel. Prefix them with an apostrophe, on the export path only.

    How it works

    The problem

    Text exported from a model can become executable spreadsheet syntax when a person opens the file. An answer that was harmless in a chat window may be interpreted differently in a CSV cell.

    Why it happens

    Spreadsheet applications recognise leading characters such as =, +, - and @ as formula starts. CSV quoting handles delimiters; it does not reliably tell the spreadsheet to treat those contents as plain text.

    How to work through it

    1. Apply a spreadsheet-safe transformation at the export boundary to every untrusted text cell. Prefix formula-shaped values with an apostrophe using the policy tested for the target application.
    2. Use the same protection for CSV downloads and copy-to-spreadsheet features. Leave stored source text unchanged so display and other exports do not accumulate spreadsheet-specific escaping.

    The solution

    Treat the export format as a new trust boundary. Model output, user text and retrieved content all need the same handling when they become cells.

    How to check it

    Open a harmless test export containing each trigger character in the supported spreadsheet application. The values should display as text, while ordinary text and multiline cells remain usable.

    Where this stops

    Different spreadsheet applications and import modes handle escaping differently. Consider whitespace and control characters in the policy, and test numeric fields separately rather than changing legitimate numbers blindly.

  4. Security and networking

    O_NOFOLLOW only guards the last path component.

    A symlink earlier in the path is still followed. Safe walks open each directory relative to its parent's descriptor, then fstat the final file.

    How it works

    The problem

    Rejecting a symlink at the final filename does not stop a path from passing through a symlinked parent directory. A check on the string path can also become stale before the file is opened.

    Why it happens

    O_NOFOLLOW applies to the component being opened. Earlier components may already have redirected the lookup, so a safe boundary needs control over the whole walk from a trusted directory.

    How to work through it

    1. Open the trusted root and walk each child directory relative to its parent's file descriptor, refusing symlinks at each step. Reject unsupported names and keep descriptors scoped for cleanup.
    2. Open the final entry relative to the held parent, then use fstat to verify the opened object is a regular file. Avoid blocking on pipes or devices while performing the check.

    The solution

    Bind validation to the object actually opened rather than a path checked earlier. The directory descriptors keep the traversal anchored as names change elsewhere.

    How to check it

    In an isolated fixture, replace a parent with a symlink and try a special-file leaf. Both should be refused, ordinary files should remain readable, and repeated failures should not leak descriptors.

    Where this stops

    Platform flags differ, and trusted-root selection is part of the security design. A descriptor walk does not by itself establish file ownership, acceptable permissions or content safety.

  5. Security and networking

    Env-var filtering is a seatbelt, not a sandbox.

    Stripping variables cuts accidents. It doesn't stop a process that goes looking for another one's environment.

    How it works

    The problem

    A subprocess often inherits credentials it never needed. A helper that prints its environment or forwards it to another tool can then leak a parent process's secrets by accident.

    Why it happens

    Environment inheritance is automatic unless the launcher supplies an explicit replacement. Starting with an empty mapping and adding permitted names makes every inherited capability a deliberate choice.

    How to work through it

    1. Define the variable names the child needs, such as the executable search path and required locale settings. Keep secret values out of the allowlist configuration itself.
    2. Build the child environment from that list at the shared launch boundary. Review explicit exceptions and make direct unfiltered subprocess launches fail a structural check where practical.

    The solution

    Use the allowlist to reduce accidental exposure and make inheritance reviewable. It is a useful default even for helpers that are otherwise trusted.

    How to check it

    Launch a test helper with canary secrets in the parent environment. Required variables should arrive, and the unlisted canaries should be absent from the child's environment.

    Where this stops

    This is not process isolation. A same-user child may still read credential files or process metadata, and it may have network access; stronger threats require controls beyond environment filtering.

  6. Security and networking

    A wrong tag key gives you $0 forever.

    Budget tag filters need the user: prefix. Get it wrong and there's no error, just a budget that never moves. Prove it with real spend.

  7. Security and networking

    A force-push doesn't erase old commits from the host.

    They stay reachable by ID until the host garbage-collects. Rotate the leaked secret; rewriting history is cleanup, not the fix.

    How it works

    The problem

    A secret removed from the current branch can still be read from an older commit. A clean checkout therefore gives a misleading sense that the exposure has ended.

    Why it happens

    A force-push moves references. It does not make every copy of the old Git objects disappear, and the hosting service may still serve an object by its identifier.

    How to work through it

    1. Revoke or rotate the exposed credential first, then check which services and users need the replacement. Keep secret values out of the incident notes.
    2. Rehearse the history cleanup in a scratch copy, retain a protected backup and coordinate other clones so they cannot merge the old history back.

    The solution

    Treat revocation as the control that stops use of the credential, and history cleanup as a separate task that reduces further disclosure.

    How to check it

    Check that the old credential is rejected and the replacement works only where intended. Scan the rewritten history and ask the host about retained objects when needed.

    Where this stops

    A rewritten repository cannot recall forks, clones or downloaded copies. Rotation addresses credentials; a leaked name or document needs a separate response.

  8. Security and networking

    Anything on the command line is public.

    Every user on the host can read argv. Pipe secrets and sensitive names in through stdin.

    How it works

    The problem

    Passing a token as a command-line option can expose it to another user inspecting processes on a shared host. Removing it from application logs leaves that separate channel open.

    Why it happens

    The operating system keeps the argument vector, often called argv, as process metadata. On hosts without process-visibility restrictions, peers can read that metadata while the process runs.

    How to work through it

    1. Check whether the receiving program supports standard input or a protected credential channel. Use a harmless canary value when testing the launch path.
    2. Send the value through that channel and keep it out of the argument list, shell tracing and error messages. Limit who can read any credential file used along the way.

    The solution

    Keep sensitive launch data separate from the command that identifies the program. This makes process listings useful without turning them into a credential directory.

    How to check it

    Inspect the running canary process and its captured launch arguments. The canary should reach the receiver while remaining absent from process arguments and diagnostic output.

    Where this stops

    Process visibility varies by host. Standard input is not a full sandbox, and another process running as the same user may have broader access.

  9. Security and networking

    Bedrock has no dollar cap.

    Only token rate limits, and budgets lag by hours. The only hard stop is a gateway in front, and only for traffic that goes through it.

    How it works

    The problem

    A budget alert can arrive after spending has already crossed the amount an operator meant to enforce.

    Why it happens

    Provider budgets use delayed billing information, while token rate limits constrain throughput rather than dollars. A gateway can enforce its own allowance only on requests that actually pass through it.

    How to work through it

    1. Inventory every invocation path and its identity.
    2. Separate delayed budget backstops from request-time gateway enforcement.
    3. Check for direct routes that bypass the gateway's accounting and admission rule.

    The solution

    Illustrative: a harness behind a gateway can be stopped while a separate direct client continues spending. Calling the gateway limit a whole-account cap would overstate its reach.

    How to check it

    Use a controlled allowance to test admitted and denied gateway requests, then verify that the report states which routes were covered. Record reporting lag separately from enforcement behavior.

    Where this stops

    Concurrent in-flight calls, price estimation and billing adjustments can affect exact totals. Current provider features and gateway settings must be checked before making a hard-dollar guarantee.

  10. Security and networking

    TIME_WAIT sockets can fool peer matching.

    Matching a local connection to a user by address alone can pick a stale closed socket. Match the full four-tuple and the ESTABLISHED state.

    How it works

    The problem

    A local peer-identity lookup can attribute a browser request to the wrong process if it matches only part of a connection.

    Why it happens

    Closed sockets can remain in TIME_WAIT, and several connections can share an address. A partial match can select stale or unrelated state rather than the live request's peer.

    How to work through it

    1. Match source address, source port, destination address and destination port.
    2. Require the live ESTABLISHED state for this lookup.
    3. Resolve ownership only from an unambiguous supported match and refuse otherwise.

    The solution

    Illustrative: a stale socket with the same address should not compete with the request's full connection tuple. The ports and state identify which connection is actually being examined.

    How to check it

    Exercise a live connection alongside stale and ambiguous recorded entries. Only the intended established tuple should resolve, while missing or conflicting evidence should fail visibly.

    Where this stops

    This mechanism is scoped to a supported local topology and visible kernel state. Proxies, network clients and connection races can break the assumption; use an appropriate authenticated identity mechanism for those paths.

  11. Security and networking

    AWS CLI v2 ignores the system trust store.

    It ships its own CA bundle, so a proxy root added to the OS does nothing. Set ca_bundle or AWS_CA_BUNDLE.

    How it works

    The problem

    A command-line client can reject an intercepted TLS connection after the same certificate works in a browser.

    Why it happens

    AWS CLI v2 uses its own certificate configuration rather than automatically inheriting every operating-system trust change. The client can therefore remain unaware of an approved proxy root.

    How to work through it

    1. Identify the CA bundle the CLI actually uses.
    2. Configure ca_bundle or AWS_CA_BUNDLE to include the approved trust chain.
    3. Check each intended profile and the noninteractive environment that will run the command.

    The solution

    Illustrative: updating the operating-system store may repair one client while leaving an AWS login unchanged. Configuring the CLI's explicit bundle addresses that client-specific trust boundary.

    How to check it

    Repeat a small HTTPS-backed AWS operation with certificate verification enabled. Expect the trust error to disappear before judging credentials or service permissions.

    Where this stops

    A valid bundle does not grant access or repair an expired session. Keep the approved root source and bundle maintenance clear, since a stale custom bundle can become another source of failures.

  12. Security and networking

    Scan history for secrets, not just the tree.

    Before the first push, check every commit. A secret deleted last week is still in the repo.

    How it works

    The problem

    A repository can expose a deleted credential on its first push even when its current files look clean.

    Why it happens

    Git history retains earlier snapshots. Removing a value from the working tree does not remove it from reachable commits, and sending those commits sends the earlier value too.

    How to work through it

    1. Scan the working tree and the history that will be published.
    2. Check that the scanner actually ran and selected the intended commits.
    3. If a credential is found, handle revocation and history cleanup as separate tasks before publishing.

    The solution

    Illustrative: a token added in one commit and removed in the next still appears when the first commit is inspected. A head-only scan misses it.

    How to check it

    Use a harmless planted fixture to confirm that the history gate catches an added-then-deleted secret pattern and does not silently skip when its scanner is unavailable.

    Where this stops

    A scan is a detector, not proof that no secret exists. Rewriting history does not revoke a credential or erase copies already received elsewhere; those require their own response.

  13. Security and networking

    Serverless doesn't mean zero idle cost.

    OpenSearch Serverless bills capacity for as long as the collection exists. Compare standing hourly floors before per-query prices.

    How it works

    The problem

    A low-query service can still produce a material bill while sitting idle.

    Why it happens

    Serverless describes who manages capacity, not whether reserved or minimum capacity is free. A collection can have a standing hourly floor before query and ingestion charges are counted.

    How to work through it

    1. Compare minimum billed capacity before per-request prices.
    2. Estimate the cost of keeping the collection alive across the expected month.
    3. Choose a backend whose standing cost and capabilities match the workload.

    The solution

    Illustrative: an occasionally used knowledge base may spend more on its idle floor than on all its queries. A usage-priced alternative can change that trade-off even if individual requests cost more.

    How to check it

    Use the current regional pricing and chosen configuration to calculate idle and active scenarios separately. Check actual billing categories after a controlled pilot.

    Where this stops

    Prices and minimum capacities change. A lower idle bill does not establish equivalent search features, latency, operational behavior or total cost at sustained load.

  14. Security and networking

    'Invalid token' can be a TLS error.

    gh auth status reports any failure as a bad token. Check for a certificate error before you log in again.

    How it works

    The problem

    An authentication status command can call a token invalid when its request never reaches the authentication service.

    Why it happens

    Certificate verification happens before the server can judge credentials. A wrapper may flatten that transport error into a generic authentication failure, sending troubleshooting down the wrong path.

    How to work through it

    1. Read the underlying error before replacing credentials.
    2. Check the certificate chain and the client's actual trust store.
    3. Install the approved trust root or configure the supported CA bundle, then retry the same request.

    The solution

    Illustrative: if HTTPS fails with a certificate error while an independently configured SSH path works, changing the token will not repair the HTTPS trust chain.

    How to check it

    Expect certificate validation to complete before interpreting the service's authentication response. Keep transport success and credential acceptance as separate observations.

    Where this stops

    A TLS fix does not prove the token is valid. Client trust stores differ, and certificate verification should remain enabled while the missing trust configuration is corrected.

  15. Security and networking

    SELECT ... INTO is a write.

    A blocklist that trusts anything starting with SELECT lets writes through. Use a read-only transaction and a statement timeout instead.

    How it works

    The problem

    A query tool can allow writes even though its text begins with SELECT.

    Why it happens

    A leading keyword is not a semantic security boundary. SELECT INTO and called functions complicate a string-based allow rule, and expensive reads can still exhaust resources.

    How to work through it

    1. Use a least-privilege database role.
    2. Execute each request inside a read-only transaction with a local statement timeout.
    3. Retain explicit limits for result size and operations that read-only mode does not constrain.

    The solution

    Illustrative: a query-shaped string should still be rejected by the database if it attempts a persistent write. The text filter becomes an additional check rather than the only protection.

    How to check it

    On a disposable database, try an allowed read, a forbidden write-shaped statement and an overlong query. Expect the read to succeed and the other cases to fail within their stated limits.

    Where this stops

    Read-only transactions do not forbid every side effect, such as advisory locks. Review the permitted functions and connection behavior as well as transaction mode.

  16. Security and networking

    Match on tags, not lists of ARNs.

    Listing each model in an IAM policy hits the size limit at around fifty. Matching a tier tag scales to any number.

    How it works

    The problem

    An authorization policy can run out of room as its list of model resources grows.

    Why it happens

    Enumerating resource identifiers makes policy size grow with the catalogue. A resource-tag condition expresses the tier rule once, keeping the authorization rule separate from the model count.

    How to work through it

    1. Choose a tier tag and assign it to the supported resources.
    2. Restrict invocation through a condition matching the intended tier.
    3. Check who can change the tag as part of the same authorization design.

    The solution

    Illustrative: adding another model to a tier should require correctly tagging its application profile, rather than adding another resource string to every caller's policy.

    How to check it

    Probe a permitted tier resource, another tier and an untagged resource. Expect only the intended tier to be available through the tested role.

    Where this stops

    Tag conditions depend on service and action support. Constant policy size does not mean unlimited service capacity, and another unrestricted invocation path can defeat the intended isolation.

  17. Security and networking

    A good policy on one host protects nobody.

    The design was never the weak part. The strong policy simply never reached the machines people actually worked on, so distribution is the control.

    How it works

    The problem

    A policy can be well designed and still provide little practical coverage across a fleet.

    Why it happens

    The configuration, hook scripts and log locations must reach the hosts where work runs. A pilot host proves that one installation exists; shared user data can remain exposed from another ungoverned host.

    How to work through it

    1. List the hosts and entry points that must be covered.
    2. Deploy the complete policy artifact set through the managed installation path.
    3. Check the installed files and behavior on the actual working hosts.

    The solution

    Illustrative: validating a policy on a test workstation does not establish coverage on a desktop used every day. Include both in the inventory and record the observed deployment state separately.

    How to check it

    For a representative host in each supported class, inspect policy presence, hook availability and an approved behavior check. Missing coverage should remain visible in the report.

    Where this stops

    One successful sample is not fleet-wide proof. Managed application settings also have a limited enforcement boundary and do not replace operating-system or network controls.

  18. Security and networking

    A convenient private install is a governance gap.

    Same fact, seen twice: whatever lets one user run ahead of the fleet also runs outside the fleet's policy.

    How it works

    The problem

    A private installation can let someone test a newer binary while making fleet version reports incomplete.

    Why it happens

    A package pin governs its package channel. It does not automatically select every executable a user can launch, even when broader managed policy still applies.

    How to work through it

    1. Inventory the executable each launcher actually selects.
    2. Separate approved evaluation builds from the normal fleet path.
    3. Check both the package pin and the managed version floor when changing releases.

    The solution

    Illustrative: a user-local native binary may run ahead of the managed package. Record that evaluation path explicitly rather than reporting the installed package version as proof of the running version.

    How to check it

    Launch each supported entry point and inspect its resolved executable and version. Compare those results with the intended fleet and evaluation policies.

    Where this stops

    A private binary is not proof that all security controls disappear. Managed configuration may still apply across installation methods; the gap must be described at the control it actually bypasses.

  19. Security and networking

    Skills on shared storage are an injection path.

    Anyone who can write to that folder can write instructions your agent will follow. Lock the folder or don't load from it.

    How it works

    The problem

    An agent can load apparently useful project instructions that another user has changed on a shared filesystem.

    Why it happens

    A skill is executable influence over future decisions. Anyone who can alter its loaded bytes can change what the agent is told to read, run or trust, even without changing the main task.

    How to work through it

    1. Inventory loaded skill paths and determine who can write their files and parent directories.
    2. Restrict trusted skill sources to controlled releases with protected permissions, or disable loading from paths that untrusted users can modify.

    The solution

    Keep reviewed instruction releases distinct from a collaborative working folder. Check the resolved path as well as its displayed name, because a protected-looking entry can point into writable storage.

    How to check it

    Using a harmless test instruction, confirm that untrusted shared content is not loaded. Verify trusted release identity and how existing sessions pick up or retain a policy update.

    Where this stops

    Filesystem protection reduces this injection route; it does not make every instruction trustworthy. Review content and other loading paths, and distinguish agent governance from controls a determined local user can bypass.

  20. Security and networking

    Check the search domain before calling a host missing.

    A short name can fail while the full name resolves. A reverse lookup is a cheap way to confirm.

    How it works

    The problem

    A failed lookup of a short hostname can be mistaken for a missing machine when the fully qualified name resolves normally.

    Why it happens

    The short form depends on the client’s search domains and resolver configuration. A resolver that does not know the relevant private zone cannot infer the intended full name.

    How to work through it

    1. Inspect the configured search domain and try the expected fully qualified hostname with the active resolver.
    2. If it resolves, inspect the address’s reverse record as a consistency check and use the confirmed full name for the next connection test.

    The solution

    For a placeholder host called build, compare build with build.example.internal before changing DNS or declaring the host absent. Keep the exact name used in diagnostics.

    How to check it

    Record forward lookup results for both names and any reverse mapping. Then test the intended service separately: successful DNS resolution is only a name-to-address result.

    Where this stops

    Reverse DNS can be absent or stale, so a PTR record is supporting evidence rather than proof of identity or authorization. Split DNS and VPN routing may also change what each resolver knows.

  21. Security and networking

    SSH doesn't care about your CA bundle.

    SSH trusts host keys, not X.509 certificates, so git over SSH keeps working when HTTPS tools fail under TLS interception.

    How it works

    The problem

    Git over HTTPS can stop working under certificate inspection while an SSH remote remains usable on the same host.

    Why it happens

    The two transports use different trust systems. HTTPS verifies an X.509 certificate chain against trusted authorities; ordinary SSH verifies a server host key against a trusted key record.

    How to work through it

    1. Identify whether the failing remote uses HTTPS or SSH, and diagnose that transport’s actual trust failure.
    2. If SSH is an approved available route, verify the server’s host key through a trusted channel before configuring the remote and client key.

    The solution

    Use the supported SSH route when its independent trust and access requirements are satisfied. Keep the HTTPS CA problem recorded, because changing the Git remote does not repair other HTTPS tools.

    How to check it

    Check the effective remote URL and establish an authenticated SSH connection to the intended server. A changed host-key warning should remain an error to investigate, not something the workaround disables.

    Where this stops

    SSH can still be blocked by routing, host policy or missing authorization. It avoids this particular TLS trust-store dependency; it does not bypass security controls or guarantee connectivity.

  22. Security and networking

    install -d resets modes on existing directories.

    It applies mode and owner even when the directory already exists, so a deploy step quietly loosened a 0700 directory to 0755.

    How it works

    The problem

    A deployment step intended only to ensure a directory exists can also change the permissions of a directory that was already deliberately private.

    Why it happens

    install -d applies requested directory attributes to existing directories too. Existence and ownership or mode enforcement are different operations hidden behind one convenient command.

    How to work through it

    1. Decide whether deployment owns the directory’s policy or merely needs to use an existing parent.
    2. If preserving an existing directory is the requirement, avoid applying a broad install mode to it; inspect the parent and create only the missing path under an explicit policy.

    The solution

    Do not use an unconditional `install -d -m 0755` as a harmless existence check for a directory expected to remain 0700. Keep permission changes deliberate and reviewable.

    How to check it

    In a temporary tree, prepare a 0700 directory and run the proposed deployment step. Read its mode back and expect preservation if that is the contract; also test initial creation.

    Where this stops

    A check-then-create sequence can race. Security-sensitive directories may need stronger creation and ownership checks, and deployment should fail when an existing path violates its required policy.

  23. Security and networking

    Most TLS errors behind a corporate proxy are one missing root.

    An inspecting proxy re-signs traffic. Install its root once and point each tool's bundle at it, and git, pip and containers all recover.

    How it works

    The problem

    Several unrelated HTTPS clients can fail certificate verification on the same inspected network, even while the browser connects successfully.

    Why it happens

    An inspecting proxy presents certificates signed by its own certificate authority. A client that does not trust that root cannot verify the substituted chain; different clients may read different bundles.

    How to work through it

    1. Inspect the presented chain and obtain the approved inspection root through the organisation’s trusted distribution route.
    2. Add it to the relevant trust stores and configure each affected client or container to use the intended bundle.

    The solution

    Fix trust at the client’s real boundary. A host trust-store update does not necessarily reach a Python package bundle or a container image, so trace each failing path.

    How to check it

    Retry the original verified HTTPS operation for each affected tool. Check that it succeeds with verification enabled and that the chain still terminates at the intended trusted authority.

    Where this stops

    One missing root is a useful shared explanation, not a diagnosis for every TLS failure. Expired certificates, hostname mismatches and blocked protocol upgrades require different fixes.

  24. Security and networking

    A green smoke test can use a different trust store.

    The smoke test passed with the OS certificates while the real path used its own bundle and failed behind the proxy. Test the path you ship.

    How it works

    The problem

    A smoke test can pass while the shipped HTTP client still fails TLS verification on the same machine and network.

    Why it happens

    The test may use the operating system’s certificates while the application uses its own bundle. Those are different trust decisions even when both requests target the same URL.

    How to work through it

    1. Identify the client, bundle and proxy settings used by the real application path.
    2. Run the smoke test through that same client configuration, with an explicit supported override for operators who need a different approved bundle.

    The solution

    Prefer one deliberate trust configuration over a test-only workaround. For example, a successful curl request does not validate an application that silently selects certifi instead of the OS store.

    How to check it

    Exercise a verified request through the production client constructor and record which bundle it selected. Keep a failure visible when the required inspection root is missing.

    Where this stops

    Equivalent trust configuration only establishes this part of the path. Authentication, request headers and streaming behaviour may still differ, so keep the smoke test’s claim narrow.

  25. Security and networking

    Ping and TCP can succeed on a starved host.

    The kernel answers ICMP and completes TCP handshakes even when userspace is wedged. Only SSH or VNC banner timing showed the host was stuck.

    How it works

    The problem

    A machine can answer ping and accept a TCP connection while interactive services remain unusable. Treating those probes as a full health check hides the failure users see.

    Why it happens

    The kernel can handle network responses while the userspace process that should send a service banner is stalled. The connection and the application response exercise different layers.

    How to work through it

    1. Measure connection establishment separately from the time to an SSH or other expected service banner.
    2. If the banner stalls, gather process state and kernel diagnostics through an available console or monitoring route before deciding the cause.

    The solution

    Put an application-level response into the health check, with a timeout meaningful to users. Keep ping as evidence of network reachability rather than evidence of a usable login service.

    How to check it

    The probe should distinguish “cannot connect” from “connected but no banner.” Compare those observations with process waits and kernel messages when diagnosing memory pressure or blocked storage.

    Where this stops

    A missing banner does not uniquely prove userspace starvation. Service configuration, filtering and a failed daemon can look similar; the deeper evidence determines the diagnosis.

  26. Security and networking

    Hand people a group, not sudo.

    A service account runs the process. A purpose-made group may start, stop and edit its config, and nothing else. People join and leave the group.

    How it works

    The problem

    Giving each maintainer a broad personal privilege makes service access hard to review and harder to remove when responsibilities change.

    Why it happens

    The process identity and the right to manage it are separate concerns. Mixing both with a person’s account ties the service’s lifetime and authority to that person.

    How to work through it

    1. Run the service under a dedicated service account and list the exact management actions its maintainers need.
    2. Grant those actions to a purpose-made group, then manage access through membership and review the writable configuration that group controls.

    The solution

    A maintainer role might need to restart one service and edit its validated configuration. It does not automatically need permission to manage every service or launch an arbitrary privileged command.

    How to check it

    Test an allowed action as a group member and an unrelated action that must be denied. Remove a test member and confirm how revocation takes effect for new and existing sessions.

    Where this stops

    Some policy tools cannot distinguish every verb. Writable service configuration can itself grant substantial power, so the allowed settings need careful review.

  27. Security and networking

    An assumed-role profile doesn't inherit ca_bundle.

    The source profile worked behind the proxy and the role profile failed TLS. Each profile needs the setting.

  28. Security and networking

    INSERT OR REPLACE is a delete in disguise.

    SQLite deletes the clashing row and inserts a new one, so a guard that blocks UPDATE and DELETE can still be bypassed.

    How it works

    The problem

    An append-only table can reject ordinary updates and deletes while still allowing a caller to replace a row. The public API may look safe even though a raw database connection can change its history.

    Why it happens

    SQLite's replacement path is not an ordinary update. A conflicting insert can remove an existing row and create another one, so guards built around the obvious write statements do not cover every mutation route.

    How to work through it

    1. Write down what append-only means for the table: existing records survive, their values remain unchanged and new records are added without replacing them.
    2. Test conflicting inserts and replacement forms through a raw connection, including conflicts involving the hidden row identity. Do not rely only on the application wrapper.
    3. Make the database-level protection refuse those replacement paths, then retain the failing examples as regressions alongside ordinary update and delete tests.

    The solution

    Reject conflicts before insertion: use a BEFORE INSERT guard that raises ABORT when the new row collides with any existing unique key or explicitly supplied rowid. Keep UPDATE and DELETE guards too; test all conflict keys through a raw connection.

    How to check it

    Insert an original record, attempt each forbidden mutation and read it back. The original values and row identity should remain intact, while legitimate new inserts still work.

    Where this stops

    Replacement can be useful for a disposable cache. The problem is using it where the contract promises that earlier records cannot be changed.

  29. Security and networking

    A normal auth error proves TLS works.

    If an unauthenticated request gets a plain 'access denied' back, the handshake succeeded, so the certificate problem is fixed.

    How it works

    The problem

    A certificate repair can be hard to judge when the next request fails for an unrelated authentication reason.

    Why it happens

    TLS verification happens before the application’s authorization response. If a verified connection receives the expected service’s normal access-denied result, the request progressed beyond the certificate problem on that path.

    How to work through it

    1. Keep certificate verification enabled while applying the intended trust bundle.
    2. Send a minimal request that reaches the actual service without exposing secrets.
    3. Classify the returned error by layer before changing credentials or application configuration.

    The solution

    For example, a normal unauthenticated service error is useful progress after a certificate-verification failure. It shows that the client accepted the certificate chain and exchanged application traffic.

    How to check it

    Confirm the response came from the intended endpoint over a verified TLS connection. Then test authorized access separately if that is within scope.

    Where this stops

    A proxy can generate its own denial, and disabled verification proves no trust repair. This observation is narrow evidence for that client and connection, not proof that every tool uses the same CA store.

  30. Security and networking

    A redirect on a WebSocket handshake means a proxy answered.

    Upgrades only accept 101. A 303 with a branded HTML page came from an inspecting proxy, not the server.

    How it works

    The problem

    A WebSocket client can fall back to another transport because the upgrade request received a redirect rather than the expected protocol switch.

    Why it happens

    A successful HTTP/1.1 WebSocket upgrade requires status 101 and the appropriate handshake headers. A 303 carrying a gateway’s HTML page indicates that some HTTP intermediary or endpoint answered instead.

    How to work through it

    1. Capture the handshake status, headers and a safe description of the response body.
    2. Compare the response with the intended application and known network intermediaries.
    3. Check an approved alternate path or transport before changing application code.

    The solution

    For example, branded filtering HTML is evidence to investigate the proxy path. It explains why the client did not establish a WebSocket even if an ordinary HTTPS request works.

    How to check it

    Confirm that the intended path returns a valid upgrade and sustains a small connection. Record fallback separately from success on the preferred transport.

    Where this stops

    A redirect alone does not prove interception; an application or login service can also redirect. Attribute the cause using the response and network evidence, and distinguish HTTP/1.1 upgrade from other WebSocket transports.

  31. Security and networking

    Once a doc is in a shared index, assume it was read.

    You can't know who already searched it. Decide what goes in up front, because removing it later doesn't undo the exposure.

    How it works

    The problem

    Removing a sensitive document from a search system does not prove that nobody already retrieved or copied it.

    Why it happens

    Indexing creates an access path and may create derived chunks, embeddings, caches or generated answers. Deletion can close future paths but cannot recall information already delivered.

    How to work through it

    1. Decide which content and audiences are permitted before indexing.
    2. Verify that retrieval enforces the intended per-user or per-group scope.
    3. Plan deletion across original documents and derived stores, while keeping exposure uncertainty explicit.

    The solution

    For example, a connector reading as one application may index documents more broadly than an individual user could read. Search must preserve the required permission boundary instead of assuming source permissions automatically follow.

    How to check it

    Test an allowed and a forbidden retrieval with representative identities. Inspect what the indexing account can see and what each search user can receive.

    Where this stops

    A retrieval test proves the cases exercised, not that past exposure never happened. Audit logs can help investigate, but incomplete logs should remain an uncertainty rather than a claim of no access.

  32. Security and networking

    Raw TCP can't pass a domain allowlist.

    Domain filtering works through an HTTP proxy. Licence servers, schedulers and SSH speak raw TCP, so they get blocked.

    How it works

    The problem

    A network policy that works for HTTPS tools can block a hardware workflow that needs a license server or scheduler connection.

    Why it happens

    An HTTP proxy understands HTTP requests and hostnames carried through that protocol. A client speaking a different raw TCP protocol cannot automatically use the same domain-filtering path.

    How to work through it

    1. Inventory each required connection by protocol as well as destination.
    2. Check which connections the sandbox’s proxy can actually transport.
    3. Use an approved network-level path for required non-HTTP traffic with explicit destination limits.

    The solution

    For example, a license checkout is not a web request merely because its server has a DNS name. The design must account for the protocol and any additional ports the service requires.

    How to check it

    Test a minimal real operation for each protocol through the intended restricted environment. Empty output or an unstarted wrapper should be recorded as inconclusive, not a network-policy pass.

    Where this stops

    Some proxies support CONNECT or other tunneling, and some products offer protocol-specific gateways. The limitation concerns the configured HTTP-only filter, not a claim that raw TCP can never be controlled.

  33. Security and networking

    A limited token beats a path deny.

    Deny rules match text, and child programs like git read their own config. A narrowly scoped token stays narrow whatever reads it.

    How it works

    The problem

    A path restriction can be bypassed in effect when an allowed child program loads the same credential through its own configuration.

    Why it happens

    Tool rules often match the operation presented to the agent interface. Programs such as Git can resolve credentials independently, so denying one named file read does not limit what an already available token can authorize.

    How to work through it

    1. Give automation the narrowest practical credential scope.
    2. Keep credentials outside the caller’s reach when a broker must enforce per-user policy.
    3. Test the actual child programs and credential helpers, not only direct file tools.

    The solution

    For example, a token limited to one repository remains limited when another client reads it. That is a property of server-side authorization, rather than the spelling of the local command used to obtain it.

    How to check it

    Use controlled allowed and disallowed operations to confirm the credential’s scope. Also check that alternative authentication paths do not silently supply a broader credential.

    Where this stops

    Limited credentials can still expose or modify data within their scope. Combine them with operating-system and network controls; a token scope is one boundary, not complete isolation.

  34. Security and networking

    READ ONLY transactions still allow advisory locks.

    Session-level advisory locks are permitted and outlive the transaction on a pooled connection, so a read-only fence must deny them by name.

    How it works

    The problem

    A PostgreSQL connection can leave a disruptive advisory lock behind even when its work ran inside a read-only transaction.

    Why it happens

    READ ONLY restricts particular database writes, but session-level advisory locks are a separate mechanism. A session lock can outlive the transaction and remain attached to a pooled connection.

    How to work through it

    1. Use a least-privilege role and an explicit allowed-operation boundary.
    2. Exclude unwanted advisory-lock functions from any query interface.
    3. Bound query time and clean pooled session state as part of connection handling.

    The solution

    For example, rolling back a read-only transaction does not make a session-level lock safe to ignore. The next borrower of that connection may inherit its consequences even though no table row changed.

    How to check it

    In an isolated database fixture, try the forbidden lock operation through the actual read-only entry point. Confirm it is refused and that returning a connection leaves no unexpected session lock.

    Where this stops

    Transaction-scoped and session-scoped locks have different lifetimes. A read-only fence also needs resource limits for long queries and sleeps; preventing row changes alone does not make arbitrary SQL harmless.

  35. Security and networking

    A Unix socket can tell you who is calling.

    SO_PEERCRED gives the peer's uid, stamped by the kernel, so a local service can identify users without a login. Clients should check the server's uid too.

    How it works

    The problem

    A local broker cannot safely authorize callers from a username they type into the request.

    Why it happens

    On Linux, SO_PEERCRED exposes credentials associated with a connected Unix-domain peer. The kernel supplies this identity, so it is stronger than request text or environment variables the caller can choose.

    How to work through it

    1. Read the peer credentials from the connected local socket.
    2. Map the uid to an allowed identity and refuse unknown callers.
    3. Have the client verify the expected server uid as well as the server checking the client.

    The solution

    For example, a credential-holding broker can receive a request from a local user while keeping the service key unreadable to that user. Authorization then uses the peer identity, not an arbitrary act-as field.

    How to check it

    Connect as an allowed and an unmapped local user, and try a socket served by the wrong account in an isolated fixture. Check that each side rejects the wrong peer.

    Where this stops

    This mechanism applies to supported local Unix sockets, not remote HTTP requests. Shared accounts still collapse identity, and peer authentication does not replace operation-level authorization.

  36. Security and networking

    Knowing who's asking isn't the same as being allowed.

    When an API authenticates the key and not the person, it can't scope by user. The layer in front has to own identity.

    How it works

    The problem

    A wrapper can know the requesting person while the API behind it still sees one broadly privileged service key.

    Why it happens

    Authentication answers who is calling at a particular boundary. If the destination authenticates only the key, per-person authorization must be enforced before the request uses that key.

    How to work through it

    1. Bind the request to identity from a trusted authentication layer.
    2. Define the allowed tools, arguments and records for that identity.
    3. Check ownership or other scope rules using current server-side data before protected access.

    The solution

    For example, a caller asking for a ticket id does not establish ownership of that ticket. A service using a broad key must check the relationship rather than trusting a user-supplied email or the model’s description.

    How to check it

    Test the same operation with an owned and an unowned record, then alter identity-like request fields. Only the authenticated identity should control authorization.

    Where this stops

    An input hook cannot remove sensitive fields from a response it never inspects. Some broad API operations may need to be excluded or placed behind a stronger filtering service.

  37. Security and networking

    The prompt is not the boundary.

    Scope has to be enforced by code the model can't talk its way past, like a hook that re-checks arguments and live ownership and fails closed.

    How it works

    The problem

    A model can follow a scope instruction most of the time while still making an out-of-scope call when context changes or injected text directs it elsewhere.

    Why it happens

    A prompt influences the decision maker; it is not an independent enforcement layer. The same model choosing arguments cannot also provide the final guarantee that those arguments are authorized.

    How to work through it

    1. Keep authentication and authorization in code outside the model.
    2. Allow only known tools and argument shapes, then check live record ownership.
    3. Refuse unknown tools, missing identity and failed ownership lookups rather than guessing.

    The solution

    For example, a hook can compare a requested record with the authenticated caller before allowing a service-key operation. The model may explain its request, but its explanation cannot override that check.

    How to check it

    Try cross-user identifiers, extra scope-changing fields and a failed ownership lookup. Each should stop at the enforcing layer, even with persuasive instructions in the model context.

    Where this stops

    The boundary also depends on protecting the credential and blocking alternate routes. If callers can read the shared key directly, the hook is a workflow guard rather than complete access control.

  38. Security and networking

    shred doesn't wipe a network drive.

    The filer may keep copies and snapshots. Revoking the secret is what actually helps.

    How it works

    The problem

    Deleting a credential file can leave a valid secret in snapshots, replicas or other copies outside the client’s control.

    Why it happens

    Overwriting a file assumes the overwritten bytes are the only readable copy. Network storage can preserve earlier versions, so local overwrite success cannot prove that the credential disappeared everywhere.

    How to work through it

    1. Identify the credential and the authority that can revoke it.
    2. Revoke or rotate it, then update the consumers that still need access.
    3. Remove obsolete local copies and inspect known background dependencies.

    The solution

    Revocation changes what the secret can do, even if old bytes survive. For example, a copied token becomes much less useful once the server refuses it; overwriting one local copy does not provide that protection.

    How to check it

    Confirm that the retired credential is rejected through an approved check and that intended replacement access works. Record any consumer whose migration remains unknown.

    Where this stops

    Revocation does not erase previously accessed data, and storage cleanup still matters. Avoid claiming secure physical erasure from a client-side command on a managed or snapshotting filesystem.

  39. Security and networking

    sendmail exit 0 means queued, not delivered.

    The local mail server accepted it. SPF or DMARC checks at the receiver can still quarantine it silently, with no bounce.

    How it works

    The problem

    A successful local send can be mistaken for a delivered message, leaving an alert path unproven.

    Why it happens

    The sendmail command hands mail to a local transfer agent. A receiver later evaluates sender authorization and domain policy, and can quarantine a message after that local handoff succeeds.

    How to work through it

    1. Separate local acceptance, remote acceptance and inbox arrival in the status report.
    2. Use an authorized sender domain and inspect its SPF, DKIM and DMARC setup.
    3. Send a controlled test to an inbox you can inspect, including its spam folder.

    The solution

    Keep the queue id and inspect receiver headers when available. SPF lists permitted senders; DKIM signs the message; DMARC checks alignment with the visible From domain. These checks happen beyond the local command’s exit status.

    How to check it

    Treat an observed test message and its headers as end-to-end evidence. If only local queue acceptance is known, report delivery as unconfirmed.

    Where this stops

    Quarantine was a likely diagnosis in the source, not a verified receiver finding. Removing a domain’s protection is not a durable delivery fix, and no test mail was sent for this rewrite.

  40. Security and networking

    A sandbox stops accidents, not attackers.

    It constrains a well-meaning agent. Stopping a hostile user is the network layer's job, at the firewall or proxy.

    How it works

    The problem

    A tool restriction can look like a security boundary while leaving the user free to launch the same operation another way.

    Why it happens

    A cooperative agent follows its tool rules. A hostile caller can choose a different process or client unless the operating system or network enforces the restriction independently.

    How to work through it

    1. Name the asset and the caller being constrained before choosing the control.
    2. Put egress policy in a firewall or controlled proxy, and restrict credentials separately.
    3. Try an ordinary child process as well as the agent’s named tools when checking coverage.

    The solution

    Use agent sandboxing to reduce accidental reach, and enforce the required boundary at the layer the caller cannot edit. For example, blocking a file-reading tool does not by itself stop another program from using an accessible credential.

    How to check it

    A useful check attempts the prohibited action through both allowed and alternate clients, and confirms that the enforcing layer records a refusal.

    Where this stops

    The headline is shorthand: hardened sandboxes can resist hostile code too. The warning concerns editable tool policy and incomplete sandbox coverage, not every sandbox design.

  41. Security and networking

    A shared service account collapses identity.

    When everyone runs as one account, the log can't say who did what. Per-person identity has to be kept above it.

    How it works

    The problem

    Audit records can name the process account correctly and still fail to identify the person responsible for a request.

    Why it happens

    When several people reach a service through one Unix account or API key, that shared identity is all the downstream system sees. A typed username does not restore authentication.

    How to work through it

    1. Authenticate the person before the request reaches the shared service.
    2. Carry a stable caller id through authorization and audit records.
    3. Record the executing agent or service separately from the person who authorized its action.

    The solution

    Keep both identities. In an illustrative record, a service executes a change while a separately authenticated user authorizes it. This preserves useful attribution without pretending the service credential belongs to one person.

    How to check it

    Send two requests as different authenticated users through the same service account. Their audit records should retain distinct callers while showing the same executor.

    Where this stops

    Attribution depends on the front layer being trustworthy and hard to bypass. Kernel uid alone cannot distinguish people who already share the same account.

  42. Security and networking

    In a group chat, a bot may only know what everyone may see.

    Its answer is visible to the whole channel, so retrieval must be limited to what every participant is allowed to read.

    How it works

    The problem

    A bot can retrieve a document for one authorized requester and then expose its contents to everyone else in a group conversation.

    Why it happens

    The answer’s audience is larger than the person asking. Retrieval authorization must account for the recipients who can read the reply, including guests.

    How to work through it

    1. Establish the audience and its access rules before retrieval.
    2. Restrict the evidence to material that may be shared with that whole audience.
    3. If that scope cannot answer the question, refuse or move the question to an appropriately authorized private context.

    The solution

    For example, a private engineering document should not enter a channel answer merely because the person mentioning the bot can read it. The channel’s guest rules need enforcement in code.

    How to check it

    Test a group with a lower-privilege participant and a document that only some members can read. Expect the group reply to exclude that document and any details derived from it.

    Where this stops

    This is a design rule, not a claim that the source’s agent already implemented it. Membership changes, cached answers and quoted history also need a clear policy.

  43. Security and networking

    NFS with sec=sys only carries 16 groups.

    AUTH_SYS sends at most 16 group IDs per request, so permissions granted through any group beyond those fail silently.

    How it works

    The problem

    A user can belong to the right Unix group and still receive an NFS permission error. A local group listing does not prove the server received that group.

    Why it happens

    With AUTH_SYS, the request carries a limited set of group IDs, conventionally at most sixteen. A required group can be absent from the transmitted credentials.

    How to work through it

    1. Confirm the mount’s authentication method.
    2. Check the groups used by the actual request and the server’s group-handling configuration.
    3. Design and verify the required access path with the filesystem administrator rather than relying only on a long local membership list.

    The solution

    For a shared control file, test access as another intended team member on the real mount. A successful owner write does not establish that the group-based operating path works for colleagues.

    How to check it

    Compare an allowed and denied user under the real NFS setup. Inspect which identity and groups the server uses before changing the file modes.

    Where this stops

    Some servers resolve supplementary groups themselves, and stronger NFS authentication changes the picture. The source’s team access path was not verified as a second user, so no successful rollout is claimed here.

  44. Security and networking

    AssumeRole needs both policies to say yes.

    The caller's identity policy and the role's trust policy must both allow it. Editing a trust template does nothing until deploy updates existing roles.

    How it works

    The problem

    A role-assumption request can fail after a trust-template edit because the caller is still missing permission or the existing role never received the update.

    Why it happens

    The intended cross-role path needs caller permission and role trust. Source templates are not the live IAM policies until deployment reconciles them.

    How to work through it

    1. Check the caller identity and its permission for the target role.
    2. Check the target role’s deployed trust policy.
    3. Deploy the intended update to existing roles, then retry using the real caller.

    The solution

    If the application role is trusted but its caller cannot request sts:AssumeRole for that target, changing trust again will not fix the missing permission. Inspect both sides of the path.

    How to check it

    Use a controlled assumption request and inspect the returned identity. Also read the deployed policies to confirm that the successful request used the intended role relationship.

    Where this stops

    Other controls, such as explicit denies, boundaries and organization policies, can still block access. Same-account resource-policy grants have nuances; use the exact IAM evaluation rules for the relationship in question.

  45. Security and networking

    One role per budget, or one cap stops everyone.

    A budget deny attaches to a principal. Two tiers sharing a role means the first to overspend blocks the other.

    How it works

    The problem

    Two budget tiers can appear separate in configuration while the first tier to hit its cap blocks both.

    Why it happens

    The deny policy attaches to an IAM principal. If both workloads assume the same role, they share the permission change regardless of which budget triggered it.

    How to work through it

    1. Map each workload to the role it actually assumes.
    2. Give independently controlled tiers separate roles.
    3. Align budget scope and action targets with those roles, then test isolation.

    The solution

    A personal assistant and a central application harness need separate role targets if one must remain available when the other is capped. Separate budget names alone do not create that boundary.

    How to check it

    In a controlled drill, deny one tier and confirm that the other role can still perform its allowed request. Check the actual assumed identities, since a guardrail on an unused role protects nothing.

    Where this stops

    Separate roles isolate the deny action, not automatically the cost calculation. A broadly filtered budget can still count both tiers, so attribution and budget filtering need their own checks.

  46. Security and networking

    Ask for a model family, not a version.

    Callers that use a family alias never need a config edit when a new version ships. The mapping moves in one place.

    How it works

    The problem

    Callers become tied to model releases when each configuration names a concrete version. Upgrading then requires finding and editing every consumer.

    Why it happens

    The version-selection rule is duplicated across callers instead of owned by one model registry or resolver.

    How to work through it

    1. Let callers request a stable family name.
    2. Resolve that name centrally to an approved, working version for the required environment.
    3. Reject unresolved aliases clearly and retain the resolved version in run records.

    The solution

    A caller can ask for an “opus” family while the registry maps it to the newest working member found by the platform sweep. The caller’s intent stays stable and the upgrade rule changes in one place.

    How to check it

    Check that all consumers resolve through the same mapping and that an unknown family fails visibly. Record the concrete model used so a past run can still be explained after the mapping moves.

    Where this stops

    A family alias favors maintained behavior over exact replay. Use a recorded concrete version where reproducibility requires it, and do not promote an untested release merely because it is newer.

  47. Security and networking

    A 'read' capability overrides every file mode.

    CAP_DAC_READ_SEARCH skips read and directory-search checks everywhere, so isolation by file modes only holds against processes without it.

    How it works

    The problem

    A service with a broadly named read capability can bypass the file modes that were supposed to isolate users’ data.

    Why it happens

    CAP_DAC_READ_SEARCH bypasses normal discretionary read and directory-search permission checks on reachable local files. It is much broader than access to one intended log directory.

    How to work through it

    1. Inspect the effective capabilities of the actual service process.
    2. Remove the broad read capability when file modes are part of the isolation design.
    3. Grant the narrow required path through ownership, groups, ACLs or a dedicated collection location.

    The solution

    If a collector only needs usage records, put those records in a controlled shared directory. Giving it a capability to traverse private homes expands its authority far beyond the data it needs.

    How to check it

    Check access as the running service identity, including its effective capabilities. A mode-bit review alone cannot establish isolation when the process can bypass those bits.

    Where this stops

    This capability does not override every security mechanism. NFS server decisions, mandatory access controls and other boundaries still matter; its broad local scope is the reason to avoid treating it as a small grant.

  48. Security and networking

    Log metadata, not content.

    Prompts are private, and anyone with log access could read them. Metadata alone still attributes usage and cost to each person, cheaply.

    How it works

    The problem

    Usage logging can make private prompts and responses readable to anyone who has log access. Cost visibility does not require exposing those contents.

    Why it happens

    Invocation records can contain both operational metadata and message bodies. Enabling them together turns a metering decision into a separate data-access decision.

    How to work through it

    1. List the fields needed for attribution and cost reporting.
    2. Disable content-delivery fields unless there is a separately justified need.
    3. Set access and retention for the remaining metadata and verify a real record.

    The solution

    Keep identity, model, time and token usage where the service supports them, while leaving prompt text out. This gives an operator a way to understand usage without storing the engineering conversation itself.

    How to check it

    Inspect an emitted record for both required metadata and absent message content. Configuration flags alone are weaker evidence than the record the service actually wrote.

    Where this stops

    Metadata can still be sensitive and may not provide complete identity in every service. Content capture for agent improvement is a separate decision with its own scope and access controls.

  49. Security and networking

    Skills are words. Hooks are code.

    An instruction file can't widen permissions; a hook or tool server can run anything. Lock each down by its own risk.

    How it works

    The problem

    A plugin may look like a collection of instructions while also launching executable hooks or tool-server processes.

    Why it happens

    The bundle name hides several different mechanisms. Text can guide an agent; executable components act through the host permissions and process-launch path.

    How to work through it

    1. Inventory the instructions, commands, hooks and tool servers in the bundle.
    2. Review executable parts under the host’s process and network controls.
    3. Check actual launch behavior rather than assuming a tool-exposure allowlist prevents execution.

    The solution

    A tool server can be absent from the exposed tool list yet still be launched by the host. Treat “the agent cannot call this tool” and “this process cannot start” as separate claims.

    How to check it

    Inspect launch records in a controlled setup and compare them with the intended policy. Verify the specific boundary being claimed instead of inferring it from a configuration label.

    Where this stops

    Instruction text can still influence decisions and trigger allowed actions. The distinction is about enforcement mechanisms, not a claim that words are harmless or that every plugin has the same capabilities.

  50. Security and networking

    DNS round-robin doesn't balance clients that cache.

    Python clients resolved the name once and stuck to one replica. A real load balancer in front spread the load.

    How it works

    The problem

    Adding embedding replicas does not help if most requests still reach the same server. A shared DNS name can make this look balanced when it is not.

    Why it happens

    The clients in the source resolved the name and reused the chosen address. DNS round-robin affected resolution, not the distribution of later requests.

    How to work through it

    1. Inspect per-replica request counts under the actual client workload.
    2. Put a request load balancer in front of the replicas.
    3. Point clients at that stable entry point and check distribution again.

    The solution

    A long-lived Python client can stay attached to one resolved replica while others sit idle. A proxy such as nginx moves the balancing decision to a component that sees the requests instead of relying on repeated DNS lookups.

    How to check it

    Use the same client lifetime and connection-reuse pattern as production. Expect healthy replicas to receive work; a test that creates a fresh client for every request can hide the original problem.

    Where this stops

    The exact distribution depends on the balancing policy, keep-alive behavior and request duration. Equal replica counts alone do not prove equal work or latency.

  51. Security and networking

    A guard that finds nothing to protect should fail.

    'No git here, so nothing to check' let changes through. When a guard can't find its subject, that's an error, not a pass.

    How it works

    The problem

    A repository guard can return success when it cannot find a repository. That lets changes pass precisely when the guard has no evidence to inspect.

    Why it happens

    The guard treats missing subject matter as an empty, clean result. It never distinguishes “nothing violated the rule” from “the rule was not evaluated.”

    How to work through it

    1. Resolve the repository root before checking changes.
    2. Fail clearly if the root or required inputs cannot be found.
    3. Run the actual rule only after its prerequisites are established.

    The solution

    A check launched from outside a repository should explain that it could not run. It should not say “no git here, nothing to check” and return the same result as a clean repository.

    How to check it

    Run the guard on a clean tree, a deliberately violating tree and a directory without Git metadata. Expect success only for the clean tree, with a distinct explanation for the missing repository.

    Where this stops

    Commands intended to work outside repositories can offer an explicit optional mode. A mandatory release or safety gate should not infer that mode from a missing prerequisite.

  52. Security and networking

    Passing options straight through is a privilege.

    A config value forwarded as raw options lets whoever edits the config run anything the tool allows. Name the options you accept.

    How it works

    The problem

    A configuration field can become an unintended command surface when its value is passed straight through as tool options.

    Why it happens

    The tool interprets those options with its own authority. Whoever edits the configuration may gain behaviors the surrounding application never intended to expose.

    How to work through it

    1. List the specific behaviors the configuration needs to control.
    2. Represent them as named, typed fields with allowed values.
    3. Construct the tool arguments from those fields and reject unknown options.

    The solution

    For example, expose a timeout as a bounded numeric setting rather than a free-form “extra arguments” string. The caller can change the timeout without gaining every other operation supported by the underlying command.

    How to check it

    Try an unknown field and an out-of-range value. Expect validation to reject each before the tool starts, and check that valid settings produce only the intended arguments.

    Where this stops

    An expert-facing wrapper may deliberately expose a raw command interface. That needs an explicit trust contract; calling the input “configuration” does not make it a smaller interface.

  53. Security and networking

    On NFS, root on the client is nobody on the server.

    root_squash maps root to an anonymous user, so even uid 0 or CAP_DAC_READ_SEARCH can't read other users' private NFS homes. The server decides.

    How it works

    The problem

    A privileged process can read local files yet receive permission errors when it reaches a private home on an NFS mount.

    Why it happens

    NFS permissions are decided by the server. With root_squash, a request from client uid 0 becomes an anonymous user; local read capabilities do not override that decision.

    How to work through it

    1. Identify whether the failing path is local storage or an NFS export.
    2. Check the export’s identity and permission rules with its administrator.
    3. Choose an authorized shared location or a push-based collection path instead of assuming client root can read every home.

    The solution

    For usage collection, have each user write the allowed records to a shared spool that the service can read. This removes the need for the collector to traverse private homes.

    How to check it

    Check access using the actual service identity on the actual mount. A successful local-file read is not a check of server-side access.

    Where this stops

    Absolute paths fix path resolution, not server permissions. Changing export policy is a separate access-control decision; this lesson does not require weakening root_squash.

Data and databases

30 lessons

  1. Data and databases

    A hash chain proves a record wasn't changed. Not that it was true.

    One writer checking itself is the opposite of what makes a blockchain trustworthy. The writer needs independent checkers, and a mirror it can't rewrite.

    How it works

    The problem

    A ledger can have a valid hash chain and still contain a false statement. If the same writer controls the records, the checker and the only saved copy, internal consistency gives limited assurance.

    Why it happens

    Hash links show how records depend on earlier bytes. They do not establish who supplied those bytes, whether an event happened or whether the writer replaced the entire chain.

    How to work through it

    1. Keep one controlled writing path, but separate its authority from the checking path. Give checkers access to the evidence needed to replay the recorded rules independently.
    2. Anchor a chain head or mirror outside the writer's control and protect the writer's identity. Compare later history against that external reference instead of relying only on the writer's latest account.

    The solution

    Use hashes for change detection and independent checks for trust. Each control answers a different question, so none should be described as proving the whole ledger true.

    How to check it

    In an isolated trial, alter a past entry and attempt to rebuild the chain. The protected reference should expose the difference, and a false underlying claim should still need an evidence check.

    Where this stops

    These are design requirements, not a claim that the historical mirror or independent trust boundary was built. Independence fails if all checkers share the writer's credentials and mutable evidence.

  2. Data and databases

    Fix attribution at the source.

    Scraping logs to guess which job used what is fragile. Have the flow record the job id and its parent when it runs.

    How it works

    The problem

    Job attribution becomes fragile when a monitoring system has to reconstruct relationships from names and logs after the run.

    Why it happens

    A tool may not record a scheduler job id, and several jobs can share the same user and host. Source-side identifiers preserve a relationship that later heuristics can only infer.

    How to work through it

    1. Record the job id and parent id when the flow launches work.
    2. Attach the feature or resource record at that same point.
    3. Keep the evidence kind with every derived lineage edge.

    The solution

    Illustrative: an inherited parent-job identifier lets a worker report its origin directly. A matching name prefix can remain a fallback, clearly labelled as inference rather than equivalent proof.

    How to check it

    Trace a known parent and worker through the recorded fields. Their relationship should be recoverable without guessing from similar names, and missing metadata should remain visible.

    Where this stops

    Adding a field does not backfill historical runs that never captured it. Instrumentation also needs owner approval and a defined scope so unrelated jobs are not accidentally linked.

  3. Data and databases

    A green SQLite suite says nothing about Postgres.

    Types, upserts and time zones differ. A test on the real engine found migrations that couldn't rebuild the live database.

    How it works

    The problem

    A migration suite can pass on a substitute database while failing to rebuild the production engine from scratch.

    Why it happens

    SQL engines differ in types, upsert syntax and timezone behavior. Tests that emulate the interface can miss engine-specific DDL and migration ordering failures.

    How to work through it

    1. Keep fast unit checks for isolated logic.
    2. Create a disposable instance of the real database engine.
    3. Apply the full migration chain to an empty database and exercise representative reads and writes.

    The solution

    Illustrative: a migration accepted by SQLite may not create the intended PostgreSQL type or conflict behavior. A fresh PostgreSQL instance tests that contract directly without touching production data.

    How to check it

    Expect the migrations to reach the current schema from an empty real-engine instance, then verify key constraints and application operations. Record the engine version used.

    Where this stops

    A clean rebuild does not test every upgrade path from existing data. Add representative populated-version upgrades where transformation risk exists, and keep the disposable target distinct from the live database.

  4. Data and databases

    A peak can't tell five minutes from twenty hours.

    Peak-only history makes a short spike look like constant saturation. Time spent at full use is what should drive a capacity decision.

    How it works

    The problem

    A single peak value cannot show whether licence capacity was exhausted briefly or throughout the working day.

    Why it happens

    A maximum discards duration. Two windows can share the same highest checkout count while imposing very different delays on users.

    How to work through it

    1. Keep occupancy over time alongside the peak.
    2. Calculate time at full capacity with sampling coverage visible.
    3. Compare that duration with actual wait or denial evidence before recommending capacity.

    The solution

    Illustrative: reaching ten seats for one sample is different from staying at ten for hours, even though both windows have a peak of ten. Duration preserves the distinction that the purchase decision needs.

    How to check it

    Compare a brief-spike fixture with a sustained-full fixture. Their peaks should match while their full-capacity durations differ, and missing samples should not count as known spare capacity.

    Where this stops

    Polling can miss short bursts and cannot prove demand from occupancy alone. A full pool may have no waiting users, so include queue or denial evidence where available.

  5. Data and databases

    Autovacuum never analyzes partitioned parents.

    The parent's statistics stay empty until an explicit ANALYZE, which can hide planner and counting bugs for weeks.

    How it works

    The problem

    A partitioned database can have incomplete parent statistics even while its leaf tables receive routine maintenance.

    Why it happens

    Autovacuum's analysis of changed leaf tables does not automatically establish up-to-date statistics for a partitioned parent. Separate counting logic can then accidentally depend on absent parent estimates.

    How to work through it

    1. Inspect parent and leaf statistics separately.
    2. Schedule an explicit ANALYZE for partitioned parents where needed.
    3. Exclude partitioned parent estimates when summing leaf relation counts.

    The solution

    Illustrative: an unknown parent row estimate can hide a double-counting bug until manual analysis populates it. Adding maintenance without checking the aggregate may suddenly change a dashboard total.

    How to check it

    Compare the aggregate's relation-kind filter and representative parent statistics before and after analysis. The total should count stored rows once, not both parent and leaf estimates.

    Where this stops

    Planner estimates are not exact row counts. ANALYZE improves statistics but does not fix an aggregate that sums the wrong relations; those are separate corrections.

  6. Data and databases

    Claim before you send.

    Mark the items in the ledger first, then build the message from exactly those, so a retry can't send twice.

    How it works

    The problem

    A retrying digest worker can send overlapping messages if it selects candidates independently on each attempt.

    Why it happens

    The candidate query can change between attempts, and parallel workers can select the same items. A ledger claim gives one attempt a fixed set of work before the message is built.

    How to work through it

    1. Atomically claim the eligible items in the ledger.
    2. Build the message only from the items that attempt actually claimed.
    3. Record the send state and define how interrupted claims are reconciled.

    The solution

    Illustrative: two workers may discover the same ten items, but only one should acquire each claim. Both must compose from their acquired ids rather than the original candidate list.

    How to check it

    Rehearse competing claims and crashes before and after delivery handoff. Check for duplicate attempts and make unresolved delivery states visible.

    Where this stops

    Claim-before-send alone cannot guarantee exactly-once external delivery. A crash after claiming can lose a send, while an uncertain delivery acknowledgement needs an explicit retry or reconciliation policy.

  7. Data and databases

    A join that returns the wrong match is worse than none.

    An empty result makes you look. A plausible wrong match looks like it works and quietly poisons everything downstream.

    How it works

    The problem

    A join can silently attach measurements to the wrong job when normalization makes distinct hosts look identical.

    Why it happens

    A hostname suffix may be removable, but an IP address's dots are part of its identity. Treating both as dotted names can collapse several addresses to the same short key.

    How to work through it

    1. Preserve the raw host value.
    2. Normalize hostnames and IP literals with different rules.
    3. Require all available matching evidence and retain ambiguity rather than inventing a single confident link.

    The solution

    Illustrative: shortening two different dotted IP addresses to their first component creates a plausible match that never represented the same machine. Keeping each literal whole avoids that collision.

    How to check it

    Test distinct IPs, short hostnames and fully qualified names. Inspect known joins and intentionally ambiguous candidates; only supported unique matches should be attributed.

    Where this stops

    Even a unique user-host-time match can be inferred when the source records no job id. Label the evidence level and prefer source-recorded identity when it becomes available.

  8. Data and databases

    SET statement_timeout sticks to a pooled connection.

    The next request on that connection inherits it. Use SET LOCAL inside a transaction instead.

    How it works

    The problem

    A short timeout chosen for one request can unexpectedly affect a later request that borrows the same database connection.

    Why it happens

    Plain SET changes session state. Connection pooling reuses that session, so returning the connection does not necessarily erase the request's configuration.

    How to work through it

    1. Start a transaction for the bounded query.
    2. Apply SET LOCAL statement_timeout inside that transaction.
    3. Commit or roll back before returning the connection to the pool.

    The solution

    Illustrative: one analytical request needs a strict timeout while a later report has a different budget. Transaction-local configuration keeps the first request's choice from leaking into the second.

    How to check it

    Borrow the same connection for two sequential transactions and inspect the effective timeout in each. The second should use the intended baseline after the first ends.

    Where this stops

    SET LOCAL depends on an actual transaction boundary. Review autocommit behavior and error cleanup; a connection left in a failed transaction can create a separate pool-reuse failure.

  9. Data and databases

    A materialised view can carry an index. A plain view can't.

    The plain view recomputed everything on every read. Its materialised twin cached the work and turned max() into an index seek: 12 s down to 21 ms.

    How it works

    The problem

    A freshness query can spend seconds recomputing an expensive view just to find its latest timestamp.

    Why it happens

    A plain view stores a query definition. A materialized view stores its result and can carry an index, letting a suitable aggregate use that stored structure instead of repeating the whole computation.

    How to work through it

    1. Compare the query plans for the plain and materialized versions.
    2. Choose the stored result when its refresh age fits the endpoint's needs.
    3. Bound the probe and expose probe failure separately from stale data.

    The solution

    Illustrative: finding a maximum timestamp over an indexed materialized result can avoid recomputing a complex join. The original timings belong to one reported comparison, not a general speed guarantee.

    How to check it

    Use EXPLAIN ANALYZE on representative data and inspect both the scan and elapsed work. Also check that refresh age is acceptable to the freshness contract.

    Where this stops

    A materialized result can be stale and needs refresh ownership. A scoped live query may be faster or more current for some workloads, so measure the alternatives.

  10. Data and databases

    GREATEST ignores NULL in Postgres.

    GREATEST(NULL - x, 0) returns 0, so a missing value reported a confident zero waste. Handle NULL before you compare.

    How it works

    The problem

    A missing requested-memory value can turn into a convincing zero-waste result.

    Why it happens

    In PostgreSQL, GREATEST ignores null inputs when another argument is nonnull. Subtracting from a missing value yields null, but comparing that result with zero can still return zero.

    How to work through it

    1. Check required inputs for null before calculating waste.
    2. Return null when the inputs do not establish a value.
    3. Keep that missing state distinct through the API, chart and export.

    The solution

    Illustrative: CASE WHEN requested IS NULL OR used IS NULL THEN NULL ELSE GREATEST(requested - used, 0) END preserves uncertainty before applying the lower bound.

    How to check it

    Evaluate missing inputs, genuine zero waste and positive waste as separate cases. The missing case should remain visibly unknown rather than producing a numeric zero.

    Where this stops

    Other database engines can handle GREATEST differently. A null-safe calculation also needs a clear policy for failed runs whose measured use may only be a lower bound.

  11. Data and databases

    Always end ORDER BY with a unique key.

    Sorting on a column with few distinct values let the same rows land on two pages. A unique tiebreaker makes paging stable.

  12. Data and databases

    A backup that exits 0 can be empty.

    Check a dump by listing what's inside it, not by its exit code.

    How it works

    The problem

    A backup job can finish without producing an archive that contains recoverable table data.

    Why it happens

    Exit status and file presence describe the backup process, not the contents needed for recovery. An empty or incomplete artifact can pass a shallow existence check.

    How to work through it

    1. Check the backup command and service result.
    2. List the archive contents with the appropriate restore tool.
    3. Compare the listed schemas and table-data entries with the expected database inventory.

    The solution

    Illustrative: a custom-format PostgreSQL dump should list the expected TABLE DATA entries through pg_restore -l. A file that exists but lists no required data is not a usable backup.

    How to check it

    Alert on an unreadable archive or missing required entries, and perform a restore rehearsal on a separate instance when validating recovery readiness.

    Where this stops

    A contents list is a necessary structural check, not proof that restoration succeeds or that every row is intact. Partition growth can legitimately change the expected entry count.

  13. Data and databases

    Use UTC everywhere, and label it.

    Host-local summer time put the same wait on two different dates in one report. Labelled UTC keeps day buckets stable.

    How it works

    The problem

    The same event can fall on different displayed dates when a report mixes local time with UTC day buckets.

    Why it happens

    Day boundaries belong to a timezone. Summer-time offsets can move an event across midnight, so a locally formatted timestamp may disagree with the server's grouping.

    How to work through it

    1. Parse source timestamps using their actual timezone.
    2. Convert them to UTC before grouping and comparing.
    3. Label every displayed timestamp and date range with the timezone used.

    The solution

    Illustrative: an event shortly after local midnight may still belong to the previous UTC date. A UTC-labelled table and a UTC-labelled chart can then agree about which bucket contains it.

    How to check it

    Check events around midnight and a daylight-saving transition. The same instant should retain one UTC bucket across the API, export and visible report.

    Where this stops

    UTC cannot repair a timestamp that was originally parsed in the wrong zone. A local-time business report may be valid, but its grouping and labels must use that zone consistently.

  14. Data and databases

    A read path that repairs is a write path.

    A reader can't tell a live writer from a dead one, so only the writer should recover a crashed store.

    How it works

    The problem

    A consumer opening a supposedly read-only store can interfere with a live writer if it decides an unfinished journal means a crash.

    Why it happens

    The reader may not be able to observe the writer’s lock across the deployment boundary. Absence of visible lock evidence is not proof that the writer is dead.

    How to work through it

    1. Keep recovery in the single writer’s controlled open or startup path, where exclusive ownership can be established.
    2. Make readers validate and report incomplete or unavailable state without modifying it; route repair requests to the writer-owned recovery mechanism.

    The solution

    If a reader encounters an unfinished transaction, return a clear unavailable result or use a supported consistent read view. Do not silently promote that reader into a repair process.

    How to check it

    In a disposable concurrency test, hold the writer active and open a reader. Expect the reader to leave journal and data bytes untouched. Separately test writer startup recovery after a simulated crash.

    Where this stops

    This advice concerns a store whose reader cannot establish safe recovery ownership. Database engines have their own documented recovery protocols; do not replace them with a hand-written lock guess.

  15. Data and databases

    EXPLAIN ANALYZE really runs the statement.

    It executes the query to time it, so wrap writes in a transaction and roll back. Add BUFFERS to see page counts, not just time.

    How it works

    The problem

    An operator inspecting a database write can accidentally perform it when asking for an execution plan with timings.

    Why it happens

    PostgreSQL EXPLAIN ANALYZE executes the statement to gather actual run information. The word “explain” does not make the operation read-only.

    How to work through it

    1. Prefer a representative disposable environment when analysing a write, and inspect its possible side effects first.
    2. For transactional changes, use an explicit transaction around `EXPLAIN (ANALYZE, BUFFERS) ...` and finish with ROLLBACK instead of COMMIT.

    The solution

    Use ordinary EXPLAIN when an estimated plan is sufficient. Add ANALYZE deliberately when actual execution is needed, and use BUFFERS to inspect block activity alongside timing.

    How to check it

    On test data, compare affected rows before and after rollback and inspect the reported plan. Confirm the transaction ended; a copied command sequence is not evidence that the write was undone.

    Where this stops

    Rollback does not reverse every effect: sequence advances, external effects and some function behaviour may survive or matter during execution. Locks and load still occur, so a transaction is not a substitute for a safe test environment.

  16. Data and databases

    Split the schema before you have data.

    A storage layout is cheapest to change while nothing is committed and no corpus exists. After that, every split is a migration.

    How it works

    The problem

    A document record can become awkward when simple descriptors and large nested knowledge payloads are forced into the same storage format.

    Why it happens

    Different data has different ownership and update needs. After a corpus exists, separating those concerns requires migrating old records and preserving every existing reference.

    How to work through it

    1. Before committing a corpus, separate document descriptors from derived knowledge and define the identity linking them.
    2. Specify how a payload binds to the exact source bytes, then update writers and readers together before data depends on the layout.

    The solution

    For example, keep descriptors with the document and a structured knowledge payload separately, joined by a document id and Markdown digest. The digest exposes a payload built from an older document version.

    How to check it

    Use one sample document to test both records, their join and a source edit that must mark the old payload stale. Check that readers reject mismatched identity or content versions.

    Where this stops

    Early change is cheaper, not free. Do not split storage without a real ownership or format need; once data exists, plan an explicit migration rather than assuming it can be regenerated safely.

  17. Data and databases

    Bracket your ORs.

    A AND B OR C means (A AND B) OR C in most query languages. That silently dropped a date filter. Brackets make the intent explicit.

    How it works

    The problem

    A query meant to select recent records in either of two categories can include records outside its date range.

    Why it happens

    In many query languages AND binds more tightly than OR. `recent AND category_b OR category_c` therefore admits every category_c record, regardless of the recent condition.

    How to work through it

    1. Write the intended grouping explicitly: `recent AND (category_b OR category_c)`.
    2. Place the grouping in the shared query builder, then inspect each caller that relies on its filters rather than patching only one report.

    The solution

    Use parentheses to make the common filter apply to both alternatives. Treat a change in result count as a reason to inspect membership, not automatic proof that either query is correct.

    How to check it

    Build four small cases: recent B, recent C, old B and old C. Expect only the first two. Add an unrelated category to confirm that the OR did not broaden the query beyond its intended set.

    Where this stops

    Operator rules and query syntax vary. Check the actual engine, and keep NULL behaviour and boundary timestamps as separate cases where they matter.

  18. Data and databases

    Never report a mean for skewed durations.

    Resolution times have long tails, so the mean misleads. Give n, coverage and method alongside any figure you report.

    How it works

    The problem

    A single average resolution time can conceal a queue where most work finishes quickly and a few cases remain open much longer.

    Why it happens

    A long tail can pull the arithmetic mean away from the typical case. Missing, contaminated or selectively collected durations can make even a median untrustworthy.

    How to work through it

    1. Inspect the duration distribution and define which records qualify before choosing a summary.
    2. Return the sample count, coverage and calculation method with any statistic; refuse the figure when the sample cannot support the requested interpretation.

    The solution

    For a report about typical completion, consider a median and useful percentiles alongside the count, rather than presenting a bare mean as the whole story. State how unresolved cases were treated.

    How to check it

    Check a small dataset with one extreme duration and compare the summaries. Then remove records deliberately and confirm the report exposes reduced coverage instead of presenting the result as complete.

    Where this stops

    The original “never” is a reporting rule for this use, not a mathematical ban on means. Mean duration can matter for total workload when the data and question support it.

  19. Data and databases

    A mapping should be a join, never a hand-kept list.

    Two lists kept by hand drift apart. Derive one from the other and the question goes away.

    How it works

    The problem

    A hand-maintained mapping from requirements to execution records can disagree with the records it is meant to explain.

    Why it happens

    Two lists age independently. Once the identities and relationships already exist in the data, copying them into another list creates a second maintenance task without adding evidence.

    How to work through it

    1. Choose stable identifiers at each handoff and record the relation where it is created.
    2. Generate the mapping by joining those identifiers, and report missing or ambiguous matches instead of silently dropping them.

    The solution

    For a requirement-to-test view, derive the display from the requirement id carried by the test and the execution record carrying the test id. Keep display labels separate from join keys.

    How to check it

    Use a small fixture with a complete chain, a missing test and an unmatched execution. The generated mapping should show the valid connection and expose both gaps.

    Where this stops

    A join can faithfully reproduce bad source data. Identifier uniqueness, relation meaning and coverage still need checks; the query does not establish that a test truly verifies its requirement.

  20. Data and databases

    Never guess to fill a gap.

    Backfill with NULL or UNKNOWN, so missing stays distinct from zero and later readers can tell the two apart.

    How it works

    The problem

    A historical import can make a missing measurement look like a measured zero, and later reports may treat that invented certainty as data.

    Why it happens

    Defaults carry meaning. Writing 0, false or none says a question was answered, even when the source never supplied an answer.

    How to work through it

    1. Define a representation for unknown before importing, keeping it distinct from measured zero, known absence and broken collection.
    2. Backfill only values that can be derived from preserved evidence; keep authored facts missing until someone can supply them.

    The solution

    For an unrecorded duration, store NULL or the schema’s explicit unknown state. Do not substitute zero merely because the reporting code expects a number.

    How to check it

    Import a small sample containing a real zero and a missing value. Confirm that both survive storage, export and display as different states, and that aggregation states its treatment of unknowns.

    Where this stops

    NULL alone may not explain why data is missing. Preserve collection errors and provenance separately where they affect interpretation; unknown must not conceal a failed endpoint.

  21. Data and databases

    Too many categories is as bad as too many synonyms.

    Split a vocabulary too finely and tags scatter; leave it open and synonyms pile up. Both break grouping, so keep the list closed and short.

    How it works

    The problem

    A classification system can fail even with no spelling variants: too many nearly identical categories scatter related records across different buckets.

    Why it happens

    A free vocabulary lets names drift, while an over-split closed vocabulary asks users or models to make distinctions the documents do not support.

    How to work through it

    1. Build the first list from real records and merge categories that do not change a useful downstream decision.
    2. Keep values closed, define each boundary and provide an explicit unknown state rather than forcing a guess.

    The solution

    For decision status, use a small standard set and place separate nuance in qualifier fields. An assumed accepted decision can remain accepted with an assumption flag instead of gaining another near-synonym status.

    How to check it

    Classify a mixed sample and inspect disagreements and the unknown rate. Validate the examples against the same enum the tool enforces, including the qualifier needed to retain meaning.

    Where this stops

    A short list is not automatically the right list. Add a distinction when evidence and a consuming workflow need it, and treat the change as a versioned schema decision.

  22. Data and databases

    Read-only is a promise about a path. A hash is a fact about bytes.

    Permissions say who may write. Only a content hash tells you whether anything actually changed.

    How it works

    The problem

    A read-only release directory can pass a permissions check while the file being consumed is the wrong version.

    Why it happens

    Permissions describe who may write through a path. They do not establish the content’s identity, detect a previous replacement or prove that the consumer opened the intended file.

    How to work through it

    1. Record a content digest from the approved input and carry it with the release reference.
    2. At the consuming boundary, hash the actual bytes and compare the result with that recorded digest before accepting the artifact.

    The solution

    Use permissions to restrict writes and hashes to check identity. For example, a same-named replacement with a preserved timestamp should still be rejected when its digest differs.

    How to check it

    On disposable copies, change a byte without changing the filename, then restore the timestamp. The content check should still report a mismatch; a path or timestamp check alone is insufficient.

    Where this stops

    A matching digest proves agreement with the recorded bytes, not that those bytes are correct or trusted. The reference digest and all declared inputs need protection too.

  23. Data and databases

    SQLite's WAL mode is unsafe on NFS.

    WAL coordinates writers through shared memory on one host, which a network filesystem can't provide. On NFS, pin the rollback journal mode.

    How it works

    The problem

    A database can appear healthy on a network share while relying on coordination that only works reliably among processes on one machine.

    Why it happens

    SQLite write-ahead logging uses a shared-memory index. Putting the database on NFS does not give separate clients the local shared-memory arrangement WAL expects.

    How to work through it

    1. Identify the filesystem holding the database before choosing its journal mode; a familiar pathname may still be an NFS mount.
    2. For the supported single-writer arrangement, configure rollback journaling explicitly and read the selected journal mode back through the actual connection.

    The solution

    Make the storage assumption part of the database contract. Keep WAL on suitable local storage; reconsider the database choice when clients need shared writes.

    How to check it

    Reopen through each supported entry point and check the journal mode and recovery behaviour on a disposable database. Configuration text alone does not establish the active mode.

    Where this stops

    Rollback journaling does not make every network filesystem safe. Locking behaviour and the deployment topology still need validation; a database server may be the better boundary.

  24. Data and databases

    A recreated view loses its grants.

    DROP and CREATE make a new object with default privileges. Re-grant in the same script, and test reads, not just denials.

    How it works

    The problem

    A migration can recreate a view successfully while breaking the readers who were allowed to use it before.

    Why it happens

    DROP followed by CREATE makes a new database object with new privileges. Previous grants belong to the dropped object; default privileges may not cover the new creator or the desired access.

    How to work through it

    1. Prefer CREATE OR REPLACE when its supported changes preserve the intended object.
    2. When recreation is necessary, apply explicit grants in the same migration plan.
    3. Test the permitted role as well as a role that should remain denied.

    The solution

    For example, a reporting view can return correct data to its owner while an analyst gets permission denied. An owner-only smoke test misses the permission regression.

    How to check it

    Run the migration on a fixture database, including a repeated run, and query as the intended reader. Check schema access, view grants and the negative boundary separately.

    Where this stops

    Changing ownership or using security-sensitive view options can alter access semantics too. Successful reads must not come from granting a broader role than the design intended.

  25. Data and databases

    pptx slide order isn't in the file names.

    Reordering slides only changes sldIdLst in presentation.xml; slide1.xml keeps its name. Resolve each r:id, and shuffle fixture order so tests can catch it.

    How it works

    The problem

    A slide converter can preserve every slide’s words while presenting the slides in the wrong sequence.

    Why it happens

    PowerPoint stores presentation order in presentation.xml’s sldIdLst. Slide filenames remain stable when slides move, and each list entry resolves through a relationship id to the actual slide part.

    How to work through it

    1. Read slide ids in the order listed by sldIdLst.
    2. Resolve each r:id through presentation.xml.rels.
    3. Convert the resolved slide parts in that order rather than sorting filenames.

    The solution

    For example, a presentation whose order is slide3, slide1, slide2 must keep that sequence. Sorting the part names would produce a coherent-looking but incorrect document.

    How to check it

    Build a fixture whose filename order deliberately differs from presentation order. Check the output sequence and the independent expected result, so both sides cannot make the same filename-order mistake.

    Where this stops

    Hidden slides and custom shows need an explicit output policy. Text recall alone will not detect this ordering defect, and changing conversion order can require updating downstream expectations and structural checks.

  26. Data and databases

    CREATE SCHEMA IF NOT EXISTS checks privileges first.

    Postgres checks the CREATE permission before existence, so the 'safe' statement fails for a role without it, even when the schema is already there.

    How it works

    The problem

    A startup statement intended to be harmless can fail for a least-privilege database role even when the target schema already exists.

    Why it happens

    PostgreSQL checks CREATE permission before treating CREATE SCHEMA IF NOT EXISTS as a no-op. Existence does not bypass the privilege requirement.

    How to work through it

    1. Separate schema creation and migration from normal application startup.
    2. Run creation with the authorized deployment identity.
    3. Let the runtime role check the expected schema and use only its required permissions.

    The solution

    A read-only dashboard should not need database-wide CREATE merely to start against a prepared schema. Prepare the schema in deployment and make runtime absence a clear setup error.

    How to check it

    Test on real PostgreSQL with the actual restricted role. Confirm it can use the existing schema as intended and that the startup path no longer issues an unauthorized creation statement.

    Where this stops

    This behavior is engine-specific and can be hidden by SQLite tests. Do not solve it by granting broad CREATE unless runtime schema creation is an intentional, justified responsibility.

  27. Data and databases

    Naive timestamps take the session's time zone.

    Postgres read naive datetime parameters in the session time zone, so a summer-time shift moved every window by an hour. Pass time-zone-aware values.

    How it works

    The problem

    A time-window query can shift by an hour when a datetime without a time zone is bound to PostgreSQL under a daylight-saving session setting.

    Why it happens

    A naive value lacks an explicit offset. The database can interpret it using the session time zone, so the same wall-clock text does not necessarily mean the intended instant.

    How to work through it

    1. Parse incoming time values into timezone-aware datetimes.
    2. Choose a clear internal convention, such as UTC.
    3. Bind those typed values and test the window on the real PostgreSQL path.

    The solution

    If a request means a UTC instant, send an aware UTC value rather than a bare local-looking timestamp. The query then carries the intended instant instead of asking the session to supply its missing meaning.

    How to check it

    Use records around a summer-time transition and compare results under different session time zones. Expect the same intended instants to remain inside the requested window.

    Where this stops

    Column type and driver behavior still matter. SQLite-only tests can miss PostgreSQL binding differences, and a timestamp intended as local civil time needs an explicit, different contract.

  28. Data and databases

    NULL logic can hide a warning.

    `state='FAILED' AND exit_signal=0` is NULL when the signal is missing, not true, so the warning badge never renders.

    How it works

    The problem

    A warning badge can disappear when a database expression returns NULL instead of true or false. The display then looks reassuring while a required value is missing.

    Why it happens

    SQL has three-valued logic. With state equal to FAILED and exit_signal missing, the expression state=FAILED AND exit_signal=0 evaluates to unknown.

    How to work through it

    1. List the allowed meaning of a missing signal.
    2. Handle that case explicitly in the decoding rule.
    3. Return a defined status for the interface instead of letting NULL silently decide visibility.

    The solution

    If missing signal information should trigger an unknown warning, encode that outcome directly. Do not replace it with zero just to force a Boolean result; that would change the meaning of the record.

    How to check it

    Check the truth table for failed and non-failed states with zero, nonzero and missing signals. Expect every combination to produce the intended status and badge behavior.

    Where this stops

    The correct missing-value policy is domain-specific. Explicit handling prevents accidental suppression, but the lesson does not decide that every NULL is a failure or that missing means zero.

  29. Data and databases

    A content hash is also an address.

    Name a file by the hash of its bytes and the name tells you where it's stored and whether it changed. Git and build caches both work this way.

    How it works

    The problem

    A path can stay unchanged while the file at that path is replaced. Using the name as identity can therefore accept stale or incorrect evidence.

    Why it happens

    A content hash derives an identifier from the file’s bytes. In content-addressed storage, that identifier also determines where the object is stored.

    How to work through it

    1. Choose and record one hash algorithm and identifier format.
    2. Compute the digest from the exact bytes being stored.
    3. Use that digest as the storage key and verify it when reading the object.

    The solution

    If one byte changes, the content identifier should change even when the filename and modification time are restored. A cache can reuse the object only when its declared content identity agrees.

    How to check it

    Recompute the digest after reading and compare the full identifier. In a disposable fixture, change a byte and expect the old identifier to fail verification.

    Where this stops

    Byte identity is not semantic identity: two visually equal documents can have different bytes. Hashes also do not prove authorship or permission to use content; they establish integrity under the chosen hash assumptions.

  30. Data and databases

    Killing psql doesn't kill the query.

    The server backend keeps running after the client dies. Set statement_timeout on ad-hoc queries instead.

Hardware and EDA

35 lessons

  1. Hardware and EDA

    Peak memory follows design size, not input file size.

    Predicting from the size of the input files made forecasts worse. The tool's own log of design size was the signal.

    How it works

    The problem

    A large input file is not always a large design in memory. Compression, hierarchy and tool processing can make byte size a poor proxy for the state an EDA tool builds internally.

    Why it happens

    Peak memory follows the tool's working representation and effort, not simply the number of input bytes. A feature can look convenient to collect while contributing little predictive information.

    How to work through it

    1. Compare candidate signals against a simple baseline using earlier runs for training and later runs for evaluation. Keep the tool and job role visible so unlike workloads are not pooled blindly.
    2. Inspect design-size information in tool logs and test whether it improves the held-out forecast. Confirm that each signal exists at the point where the forecast is needed.

    The solution

    Choose predictors from measured usefulness rather than convenience. In the reported study, recorded input size did not help, while tool-reported design size was the promising signal to investigate.

    How to check it

    Compare prediction errors and the share of underestimates on the same held-out jobs. A new feature should improve the intended decision, not merely fit the training data more closely.

    Where this stops

    A log produced during execution may arrive too late for a pre-submit forecast. The source does not establish a finished design-size predictor, and missing effort data or uncertain job attribution can still limit accuracy.

  2. Hardware and EDA

    ISA compliance is won in the edge cases.

    The common path rarely breaks. Compliance is judged on corner cases, so enumerate them explicitly during analysis instead of hoping tests stumble on them.

    How it works

    The problem

    A processor or accelerator can pass ordinary instruction tests while violating the architecture at its boundaries. Integration failures often appear when hazards, exceptions and retirement interact.

    Why it happens

    The instruction set specifies observable state, not just the happy-path calculation. Decoupling execution between units adds moments when completion order and architectural order differ.

    How to work through it

    1. Enumerate the architecture's corner cases before implementing the interface. Link each case to the relevant rule for dispatch, dependency handling, exceptions or retirement.
    2. Turn the list into directed checks alongside broader test generation. Use a reference model or explicit expected state so the test checks correctness rather than merely completing.

    The solution

    Treat the boundary cases as design inputs. They determine which state must be held, which operation may proceed and when a result is allowed to become visible.

    How to check it

    For an illustrative check, delay one operation while a later one completes. Verify that the resulting architectural state still follows the specified ordering rules.

    Where this stops

    The original work is described only at CV level. This procedure does not establish compliance for a particular design, and the exact cases depend on the ISA version and implemented extensions.

  3. Hardware and EDA

    Closing the viewer doesn't stop the job.

    A GUI tool in a detached session keeps its licence until a cut-off or time limit, long after the user walked away.

    How it works

    The problem

    Closing a remote desktop viewer can leave an expensive GUI tool running. The screen disappears, yet another user still cannot acquire the licence seat held by that tool.

    Why it happens

    The viewer is only a connection to the desktop. The application and its licence checkout belong to the remote process, whose lifetime continues after the connection ends.

    How to work through it

    1. Measure the separate states: connected viewer, detached viewer, exited tool and ended job. Use the licence server and process list rather than inferring release from a closed window.
    2. Define an idle policy using both connection state and useful work. Warn before stopping an idle job, and retain a time limit as a final boundary.

    The solution

    Make session lifetime explicit. Where a tool supports releasing individual licence features, evaluate that separately from terminating the whole job.

    How to check it

    With a test seat, detach and reconnect, then exit the tool and compare licence usage. The expected distinction is that detaching preserves work while a real exit returns the seat.

    Where this stops

    A detached computation may be useful and busy for hours. Disconnect time alone is a poor kill condition, and tool-specific release commands may cause another licence wait later.

  4. Hardware and EDA

    Parse RTL with a real front end, not regex.

    Text search loses hierarchy and connectivity. A proper SystemVerilog parser that builds a syntax tree gives you the design's actual structure to reason about.

    How it works

    The problem

    Text search can find a signal name without explaining how it connects through a design. Repeated names and nested instances make that an unsafe basis for answering a hierarchy or connectivity question.

    Why it happens

    SystemVerilog carries structure that raw text matching does not resolve. A language front end builds a syntax tree and can support analysis of modules, instances and ports in their proper context.

    How to work through it

    1. Parse the RTL with a real SystemVerilog front end using the build's relevant file list and options. Keep diagnostics visible instead of silently treating an incomplete parse as a complete design.
    2. Build hierarchy and connectivity relations from that structured result, and attach source locations to the extracted facts. Use text retrieval for surrounding explanations where it helps.

    The solution

    Choose a structural representation for structural questions. A graph can then follow a relationship while retaining the source evidence for each step.

    How to check it

    Use a small known design to compare extracted instances and connections against the source. Include repeated names in different scopes so a name-only approach cannot accidentally pass.

    Where this stops

    A syntax tree alone may not resolve all elaborated behaviour. Parameters, macros and tool-specific constructs require the right analysis stage and configuration; the parser's coverage limits the graph.

  5. Hardware and EDA

    APB slaves can stretch a transfer with PREADY.

    A slow peripheral holds PREADY low to stall the access phase, so logic that assumes every APB transfer takes two cycles breaks.

    How it works

    The problem

    A peripheral bus controller can work with a fast device and fail as soon as a slower device stretches a transfer.

    Why it happens

    APB has a setup phase followed by an access phase. PREADY can keep the access phase active for extra cycles, so completing every transfer after a fixed two-cycle sequence discards the handshake.

    How to work through it

    1. Enter access after setup and keep the request selected.
    2. Hold address and control stable while PREADY is low.
    3. Complete the transfer only on the valid access handshake and sample the result then.

    The solution

    Illustrative: insert three wait cycles before the peripheral raises PREADY. The controller should remain in access through those cycles rather than advance to the next request.

    How to check it

    Simulate immediate completion and several wait lengths. Check stable request signals during the wait and exactly one completion when PSEL, PENABLE and PREADY are high.

    Where this stops

    Signal availability and error handling depend on the APB revision. Reset, back-to-back transfers and PSLVERR timing need separate cases beyond this basic wait-state check.

  6. Hardware and EDA

    Some tools hold every licence until they exit.

    A step that combines several stages keeps each earlier stage's licence to the end. Splitting the step frees them sooner.

    How it works

    The problem

    A long-lived tool shell can hold a licence long after the stage that needed it has finished.

    Why it happens

    Some tools retain checked-out features until the process exits or explicitly releases them. Combining stages in one process can therefore extend the holding time of an early-stage feature across later work.

    How to work through it

    1. Measure checkout and stage boundaries from timestamped evidence.
    2. Identify features no longer needed after a stage.
    3. Use supported release points or split the flow into restartable stage processes where appropriate.

    The solution

    Illustrative: if a three-day shell needs one feature for only its first two hours, retaining it to exit creates a large idle holding interval. A real release boundary can shorten that interval.

    How to check it

    Observe the checkout after the proposed boundary and confirm later stages still run correctly. Measure reacquisition delay as well as freed seat time.

    Where this stops

    Release behavior varies by tool and feature. A repeatedly needed feature may be better retained, and releasing it early can make the same job queue again later.

  7. Hardware and EDA

    A licence server can be up while its daemons are down.

    An lmgrd up/down check stays green while the vendor daemons that actually serve the features have failed. Check health at daemon level.

    How it works

    The problem

    A licence service can appear online while the features users need are unavailable.

    Why it happens

    The top-level manager and the vendor daemons have different roles. A running lmgrd does not prove that each daemon responsible for feature checkout is healthy.

    How to work through it

    1. Inventory the expected vendor daemons for each server.
    2. Probe and report their individual states.
    3. Keep server reachability, manager health, daemon health and data freshness as separate signals.

    The solution

    Illustrative: a green manager status alongside one failed vendor daemon should produce a degraded service verdict, with the unavailable feature group visible to the operator.

    How to check it

    Exercise a disposable or recorded response with the manager up and one expected daemon down. The aggregate status should reveal the partial failure rather than report full health.

    Where this stops

    A daemon answering does not prove every feature has available capacity. Checkout denials, expiry and collector outages require additional evidence, and unknown probe results should remain unknown.

  8. Hardware and EDA

    EDA tools stamp dates and hostnames.

    Two identical runs give different files because of the header. Hash a copy with the header stripped.

  9. Hardware and EDA

    Licence capacity is set by the scarcest feature.

    Tools that need several licence features at once saturate together, and the rarest one caps everyone.

    How it works

    The problem

    Buying more of one licence feature may not increase tool throughput when each job needs a bundle of features.

    Why it happens

    A job must acquire all required features together. The scarcest shared feature can cap concurrency even while other feature pools have unused seats.

    How to work through it

    1. Identify which features the same jobs actually hold.
    2. Compare occupancy and wait evidence across the bundle.
    3. Evaluate the limiting feature before adding capacity to a less constrained pool.

    The solution

    Illustrative: a tool needing one seat of A and one of B cannot run more than four copies if B has four seats, even when A has ten.

    How to check it

    Inspect simultaneous checkout combinations and associated wait periods. A proposed capacity increase should address the observed bundle constraint rather than the largest isolated utilization number.

    Where this stops

    Feature demand can vary by stage, tool version and seat multiplier. Correlated saturation suggests a bundle but does not by itself prove every job needs exactly one of each feature.

  10. Hardware and EDA

    Decoupled units must still retire in order.

    When a vector unit runs apart from the scalar core, dispatch, hazard checks and retirement must keep architectural state in program order, or software sees results out of order.

    How it works

    The problem

    A decoupled vector engine can finish work out of order while software still expects ordered architectural results.

    Why it happens

    Dispatch lets the scalar core and vector unit progress separately. Dependencies, exceptions and visible state updates still need a shared rule for when an instruction's result becomes committed state.

    How to work through it

    1. Define the dispatch contract and ownership of operands.
    2. Track hazards between in-flight instructions.
    3. Specify the retirement condition that makes results and exceptions visible in program order.

    The solution

    Illustrative: a later short operation can finish before an earlier long one. Completion may be recorded internally, but retirement must preserve the architecture's required ordering.

    How to check it

    Review directed dependency and exception cases against the architectural contract. A later completion should not expose a state change before an older instruction's required outcome is resolved.

    Where this stops

    The source supports the interface principle, not a detailed implementation or measured verification result. Different architectures allow different internal schedules and need their own ordering specification.

  11. Hardware and EDA

    The scheduler can't see licences checked out mid-run.

    A job gets its node, then waits on a licence the scheduler never knew about, holding memory and cores the whole time.

    How it works

    The problem

    A job can reserve cores and memory while making no useful progress because its tool is waiting for a licence.

    Why it happens

    The scheduler allocates compute before a tool performs a runtime checkout. If that licence demand is absent from scheduler accounting, the compute allocation looks active throughout the wait.

    How to work through it

    1. Separate scheduler queue time from runtime licence wait.
    2. Correlate the tool's checkout evidence with its compute allocation.
    3. Expose held compute during the wait before deciding whether scheduling or flow changes would help.

    The solution

    Illustrative: a job may receive a node immediately and then wait for a scarce feature. Its short scheduler wait does not mean its total resource wait was short.

    How to check it

    Trace a representative job from allocation through checkout to useful work. Check that the report names the two waits separately and labels inferred attribution.

    Where this stops

    Polling and indirect joins may not prove an exact job-to-checkout link. Scheduler-aware licence integration also requires accurate feature demand and an operational policy, not merely another dashboard metric.

  12. Hardware and EDA

    Sign-off tools peak where you can't restart.

    Timing and physical checks hit peak memory in phases that can't be checkpointed, so an OOM means starting over. Size from a comparable run.

    How it works

    The problem

    A long electronic-design check can lose hours of work when memory runs out at a phase with no restart point.

    Why it happens

    Some peak-memory phases cannot be checkpointed mid-step. A completed earlier stage may be recoverable, but it does not preserve progress through the phase that was killed.

    How to work through it

    1. Find a comparable successful run and inspect its peak memory.
    2. Request enough memory before launching the nonrestartable phase.
    3. Save supported stage-boundary databases and document the real restart point.

    The solution

    Illustrative: a physical check that peaks near completion may still have to restart from the beginning of that check after an out-of-memory kill. A late failure is not necessarily a cheap retry.

    How to check it

    Review the tool's supported recovery behavior and a comparable run's measured peak. The plan should say which stage can resume and which work must be repeated.

    Where this stops

    Comparable means similar design and settings. More requested memory reduces risk but is not proof of sufficiency, and scheduler changes to a running allocation may not be available.

  13. Hardware and EDA

    In distributed STA, the master reserves big and uses little.

    The coordinator mostly hands scenarios to worker jobs, so its memory request skews right-sizing. Its own launch log, not job shape, shows which workers it spawned.

    How it works

    The problem

    A distributed timing job can look wasteful when its coordinator is sized like the workers that do the analysis.

    Why it happens

    The coordinator dispatches scenarios while worker jobs hold the analysis state. Similar job names or requests do not prove the jobs share the same role or memory needs.

    How to work through it

    1. Read the coordinator's launch log to identify its workers.
    2. Separate coordinator measurements from worker measurements.
    3. Compare memory needs within the same role and a comparable scenario set.

    The solution

    Illustrative: a small coordinator and several large workers should form separate sizing groups. Reducing every job to the coordinator's observed memory would undersize the workers; treating every request as worker demand hides coordinator waste.

    How to check it

    Trace a sample launch from parent to child jobs, then inspect measured peaks by role. The reported distributed footprint should include the actual workers without counting unrelated lookalikes.

    Where this stops

    Job shape is only a clue. If launch evidence is absent, keep lineage unknown rather than deriving a confident parent-child relationship from names alone.

  14. Hardware and EDA

    Broken DFT flows are ownership problems, not tool problems.

    Engineers copy the last project's scan scripts and edit them, so nobody owns the flow and a fix has nowhere to live.

    How it works

    The problem

    A design-for-test flow can accumulate project-local fixes while the next project starts again from an older copy of the scripts.

    Why it happens

    Without an owned common flow and explicit places for project differences, a useful fix has no shared home. Changing the build tool leaves that responsibility gap in place.

    How to work through it

    1. Name an owner for the common flow and separate shared behaviour from project-specific configuration.
    2. Give shared fixes a release path and expose supported override points, then record which release each project consumes.

    The solution

    For a scan-script correction, decide whether it belongs in the common release or a documented project override before copying it. That decision gives the next maintainer somewhere to send a related fix.

    How to check it

    Trace one correction from report to shared release and then to a consuming project. Check that the project can identify its version and retain its supported differences without editing the common scripts.

    Where this stops

    This addresses ownership-driven decay. Tool defects, missing artifacts and unsuitable interfaces can still need technical changes; ownership is not proof those problems are absent.

  15. Hardware and EDA

    Never make VALID wait for READY.

    If the sender waits for ready and the receiver waits for valid, both stall forever. The source must raise valid on its own and hold it steady.

    How it works

    The problem

    A producer and a consumer can both be waiting while the interface looks quiet. If neither side raises its signal first, there is no transfer and no event to move the system forward.

    Why it happens

    In a VALID/READY handshake, the source announces that data is available and the receiver announces that it can take it. Making the source wait for READY can create a circular dependency when the receiver waits to see VALID.

    How to work through it

    1. When the source has a value to send, assert VALID independently of READY. Keep that value stable while the receiver applies backpressure.
    2. Treat a cycle with both signals asserted as the transfer. Advance to the next value only after that handshake, following the protocol's reset and channel rules.
    3. Test a receiver that holds READY low before and after VALID appears, then releases it. Include this delay in verification rather than testing only an always-ready receiver.

    The solution

    Let availability and acceptance be separate facts. The source can promise a stable value while waiting for the receiver to accept it.

    How to check it

    In the delayed-ready example, VALID must appear without a prior READY and the value must remain stable until the transfer. A scoreboard can check that the value is accepted once.

    Where this stops

    This explanation concerns an AXI-style channel contract. Do not transfer its signal rules to a different interface without reading that protocol.

  16. Hardware and EDA

    A timing violation is a symptom.

    The cause can be the RTL, the constraints, the library or the layout. Working out which is the real job.

    How it works

    The problem

    A timing failure names a path that missed a requirement, but does not by itself identify which design input should change.

    Why it happens

    The result depends on RTL structure, timing constraints, cell libraries and physical delay. A red path can therefore reflect slow logic, an incorrect requirement, a mismatched environment or implementation effects.

    How to work through it

    1. Inspect the failing path and the exact check being performed.
    2. Confirm clocks, exceptions, library and operating conditions before editing RTL.
    3. Compare relevant reports across design stages to locate where the margin was lost.

    The solution

    For example, an unexpected setup failure may come from an incorrectly constrained interface rather than a newly deep logic cone. Changing the datapath first can hide the setup mistake instead of correcting it.

    How to check it

    Rerun the relevant analysis after the supported correction and check related paths and conditions. A local improvement should not silently trade a setup problem for a hold or coverage problem.

    Where this stops

    The source frames cause attribution as an open design question, not a record of signoff work. This is an investigation order, not an automated rule that can identify every cause from one report.

  17. Hardware and EDA

    Toy constraints give toy answers.

    A synthesis run with simple SDC says little about real timing. Results are only as real as the constraints and libraries you feed in.

    How it works

    The problem

    A synthesis report can look authoritative even when its timing result describes only a simplified exploratory setup.

    Why it happens

    Synthesis optimizes the RTL against supplied libraries and constraints. Missing I/O delays, clock assumptions or timing exceptions change the problem the tool solves, so the result cannot represent requirements it never received.

    How to work through it

    1. State whether the run is an early structural check or a realistic implementation estimate.
    2. Supply the intended library and reviewed clock and interface constraints.
    3. Inspect unconstrained paths and warnings before interpreting the headline timing result.

    The solution

    For example, a simple clock constraint may be enough to test that RTL elaborates and maps. It does not establish that all real external arrival and required times will be met.

    How to check it

    Check that expected clocks and path groups exist, review constraint coverage, and compare representative paths against the design intent. Keep the run inputs with its reports.

    Where this stops

    Even realistic synthesis constraints do not replace timing analysis after physical implementation. A toy run remains useful when its scope is stated clearly and its result is not presented as final closure.

  18. Hardware and EDA

    Wrap every tool run in a script.

    An interactive synthesis or lint session can't be repeated. A script can, and a script is something an agent can run too.

    How it works

    The problem

    An interactive EDA session leaves important run choices in shell history or memory, making its result difficult to reproduce or delegate.

    Why it happens

    Tool state accumulates through commands, loaded files and environment settings. A report without that sequence does not fully describe the experiment that produced it.

    How to work through it

    1. Put the input file list, libraries, constraints and tool commands in a versioned run script.
    2. Record the tool build and relevant environment with each run.
    3. Preserve native logs and reports in a predictable output location.

    The solution

    For example, a Tcl synthesis script can read RTL, apply constraints, compile and write reports in one declared order. A wrapper can then invoke that same flow locally or in automation.

    How to check it

    Run the script from a clean working area and compare the expected outputs and tool warnings. Check that no required setting came only from an earlier interactive session.

    Where this stops

    Scripting improves repeatability but does not remove hidden external inputs or tool nondeterminism. A completed script also needs its reports inspected before the design is called correct.

  19. Hardware and EDA

    Chip design's exclusivity is artificial.

    The complexity is real. The gatekeeping isn't. More people in means more ideas and faster progress.

    How it works

    The problem

    A field can be genuinely difficult while still making entry harder than the technical problem requires.

    Why it happens

    Hidden assumptions, inaccessible examples and unexplained tool flows add learning barriers beyond the underlying engineering. Those barriers limit who can contribute without improving correctness.

    How to work through it

    1. Separate essential prerequisites from knowledge kept implicit by habit.
    2. Provide a small, legal-to-share example with a repeatable flow and checks.
    3. Explain the failure modes and limits so newcomers can learn without mistaking a toy result for production readiness.

    The solution

    For example, a simple RTL block with a scripted simulation and lint run exposes the workflow more clearly than a list of tool names. The example should show how to recognize failure as well as success.

    How to check it

    Ask a newcomer to follow the material and record where they need undocumented help. Repair those gaps while retaining the real technical requirements.

    Where this stops

    Licensing, confidentiality and safety obligations still apply. Opening the learning path does not mean exposing protected designs or claiming that chip design becomes simple once the tools are visible.

  20. Hardware and EDA

    Handshake on valid and ready, never on timing.

    On AXI, data moves only when both are high. Assuming the other side is ready 'by now' is a bug waiting for a slower clock.

    How it works

    The problem

    A producer or consumer can silently lose a transaction when it assumes that enough time has passed for the other side to be ready.

    Why it happens

    On an AXI channel, a transfer occurs only at a clock edge where VALID and READY are both asserted. The handshake, rather than elapsed cycles, establishes acceptance.

    How to work through it

    1. Advance transaction state only on the channel’s VALID-and-READY handshake.
    2. Keep payload and VALID stable while the receiver applies backpressure.
    3. Exercise variable stalls on each relevant channel in verification.

    The solution

    For example, a source holding one data beat must not replace it after an arbitrary delay if READY is still low. A slower receiver remains entitled to accept that same beat later.

    How to check it

    Use a scoreboard to compare sent and accepted beats under randomized backpressure. Add directed cases for a long stall and for readiness changing around a transfer.

    Where this stops

    AXI has several independent channels and additional ordering rules; a correct local handshake does not prove the whole protocol. This is general protocol teaching, not a claim that the cited owner’s chats record this particular bug.

  21. Hardware and EDA

    Simulates fine does not mean synthesises.

    SystemVerilog is two languages: a small synthesisable subset and a big verification one. A construct can pass simulation and fail synthesis.

    How it works

    The problem

    Passing a simulation does not establish that a hardware description can be implemented by the synthesis flow.

    Why it happens

    SystemVerilog includes both hardware-description constructs and a larger verification language. A simulator can execute behavior that has no supported circuit mapping, or whose synthesis support differs by tool.

    How to work through it

    1. Separate synthesizable design code from testbench and verification helpers.
    2. Check the constructs against the intended synthesis tool’s supported subset.
    3. Run a small synthesis check alongside simulation before integrating the block.

    The solution

    For example, a delay used to coordinate a testbench describes simulation scheduling rather than a physical delay element suitable for an ordinary RTL flow. Its usefulness in verification does not make it hardware.

    How to check it

    Elaborate and synthesize a representative design with the real constraints and libraries. Inspect warnings, inferred structures and reports instead of accepting a simulation pass as a substitute.

    Where this stops

    Tool support changes, and some verification constructs are accepted for analysis without becoming gates. This is general language guidance; the inspected topic note does not establish a specific simulation-versus-synthesis incident.

  22. Hardware and EDA

    Keep state and logic in separate blocks.

    Flops in always_ff, next-state logic in always_comb. Mixing them is how latches and sim-versus-synthesis mismatches sneak in.

  23. Hardware and EDA

    Assertions are cheap spies.

    An SVA fires on the cycle a rule breaks, not three blocks later where the bad data finally shows up.

  24. Hardware and EDA

    EDA tool versions don't sort.

    Strings like V-2023.12 and T-2022.03-SP1 have no total order, so compare them for equality, never with >=.

    How it works

    The problem

    A tool compatibility check can accept an unsupported EDA build when it sorts vendor version strings as if they were ordinary numbers.

    Why it happens

    Vendor prefixes, year formats and service-pack suffixes encode a release scheme that may not define one universal ordering. Lexical order is especially unreliable across different release families.

    How to work through it

    1. Record the exact supported tool identifier in the environment manifest.
    2. Compare by equality when exact compatibility is the requirement.
    3. Use an explicit compatibility map or documented parser when a range is genuinely needed.

    The solution

    For example, V-2023.12 and T-2022.03-SP1 should not be ordered by a generic string comparison to decide support. A named accepted build makes the decision inspectable and repeatable.

    How to check it

    Test known accepted and rejected identifiers, including service packs and an unknown prefix. Unknown forms should produce an actionable error rather than quietly passing a guessed ordering.

    Where this stops

    The headline’s “never” is scoped to unmodelled vendor versions. A documented vendor release scheme can support meaningful ranges; implement that scheme explicitly rather than assuming all versions are incomparable.

  25. Hardware and EDA

    Design for the safety standard from the first line.

    Parity and duplicated logic for functional safety shape the whole block. They are hard to bolt on later, so they belong in the first draft.

    How it works

    The problem

    A hardware block can become difficult to adapt for safety when its detection and recovery paths are left until after the ordinary datapath is settled.

    Why it happens

    Parity and duplicated logic add state, comparisons, timing paths and responses to faults. Those requirements affect interfaces and architecture, so they need design space from the start.

    How to work through it

    1. Identify the faults the block must detect and its required response.
    2. Include detection, status and recovery behavior in the first interface and state design.
    3. Plan checks that inject relevant faults and observe the specified response.

    The solution

    For example, adding parity to stored control data also requires deciding when to check it and what happens on a mismatch. A parity bit without a defined reaction is only part of the safety mechanism.

    How to check it

    Use an explicit fault model and inspect whether representative injected faults reach the detector and required response within the design’s timing assumptions.

    Where this stops

    The source records parity and an out-of-phase dual-copy design, but does not prove when those choices were made. This is architectural guidance, not a complete functional-safety argument or certification claim.

  26. Hardware and EDA

    Coverage is a number. The holes are the to-do list.

    Pushing ATPG coverage up meant chasing each uncovered fault class one by one. The percentage says how far you are; the holes say what to do next.

    How it works

    The problem

    A test-coverage percentage tells you how much fault space is covered, but gives little direction for the next repair.

    Why it happens

    ATPG generates manufacturing test patterns against a fault model. Uncovered faults have different causes: some are unreachable, some lack control or observation, and some expose a setup or design problem.

    How to work through it

    1. Group uncovered faults by the tool’s reported class and reason.
    2. Inspect representative faults and trace what prevents activation or observation.
    3. Change the design, test setup or justified exclusions, then rerun the relevant analysis.

    The solution

    For example, a group of faults behind an incorrectly constrained control should be investigated as a group. Adding patterns blindly may not help when the setup prevents the tool from exercising that logic.

    How to check it

    Compare fault identities and classifications before and after the change, alongside the percentage. Confirm that improvement came from meaningful coverage rather than quietly excluding difficult faults.

    Where this stops

    The fault model and coverage denominator matter. A higher ATPG percentage does not prove every possible silicon defect is detectable, and no new coverage result is claimed here.

  27. Hardware and EDA

    Random finds the surprises. Directed tests prove the corners.

    Constrained random stimulus covers what you didn't think of; directed cases pin down the edges you did. A test plan needs both.

    How it works

    The problem

    A random test can exercise many transactions and still miss a boundary condition already known to be dangerous.

    Why it happens

    Random generation explores combinations within its constraints. It does not guarantee that an exact edge case appears, especially when that case is rare or excluded by an unnoticed constraint.

    How to work through it

    1. Write directed tests for known boundaries, resets and error paths.
    2. Use constrained random traffic to explore legal combinations beyond that list.
    3. Compare both kinds of test against a simple reference model and record the seed on failure.

    The solution

    For example, test a queue becoming full directly, then randomize reads and writes around that boundary. The directed test protects a named rule; random traffic explores the interactions around it.

    How to check it

    Inspect coverage for the intended conditions rather than counting generated cycles. Replay a failing seed and confirm that the same stimulus reaches the same check.

    Where this stops

    A coverage hit does not prove the output was checked, and random testing only explores what the generator permits. Formal checks can complement both approaches when a property and tractable model are available.

  28. Hardware and EDA

    EDA tools were built for people, not programs.

    Before an agent can help with chip design, the tools need clean CLIs and structured output. Making the flow scriptable is step one, not a nice-to-have.

    How it works

    The problem

    An agent cannot reliably operate a hardware flow when every stage depends on an interactive session and a human reading an unstructured screen.

    Why it happens

    Automation needs stable inputs, an explicit invocation and a result that another program can interpret. Without these, the agent must guess both what to run and whether it worked.

    How to work through it

    1. Wrap one tool stage in a script with declared inputs and output locations.
    2. Preserve the native log and emit a small structured summary of status and reports.
    3. Make failure visible through a nonzero exit or an explicit failed result.

    The solution

    For example, a lint wrapper can accept an RTL file list and rule deck, run the tool, and return report paths plus violation counts. The wrapper supplies a repeatable interface around the existing EDA tool.

    How to check it

    Run the wrapper on a small valid design and a deliberately invalid fixture. Check both the summary and the native tool log, including missing-license and missing-input cases.

    Where this stops

    Scriptability is a prerequisite for useful automation, not proof that an agent understands the design or that a successful tool run establishes silicon correctness.

  29. Hardware and EDA

    Put the coding standard in the lint deck, not in people's heads.

    Teams wrote RTL their own way and paid for it at integration. A shared rule set that runs on every block fixed what review kept missing.

    How it works

    The problem

    RTL that looks reasonable within each team can become expensive to integrate when teams follow different coding rules.

    Why it happens

    Reviewers can catch individual mistakes, but repeated style and structural checks depend on who happens to read a change. A shared lint deck applies the same baseline to every block.

    How to work through it

    1. Choose a common rule set and document any project-specific additions.
    2. Run it through one scripted entry point for each block.
    3. Review waivers as named exceptions with a reason, rather than silently disabling noisy checks.

    The solution

    For example, a width-mismatch rule should produce the same result in a small unit and at subsystem integration. Sharing the deck moves that feedback to the engineer making the change, before another team inherits it.

    How to check it

    Place a known violation in a disposable fixture and confirm that the normal flow reports it. Check that each participating block actually loads the same rule configuration.

    Where this stops

    Lint does not prove functional correctness. The source supports the shared-methodology lesson, but this rewrite makes no new claim about integration time saved or particular customer designs.

  30. Hardware and EDA

    Stateless logic is the easiest logic to verify.

    In RTL, logic with no state is checked by its truth table. Software that keeps state at the edges and pure functions in the middle gets the same gift.

    How it works

    The problem

    A function that quietly reads or changes shared state needs its history reproduced before its output can be understood.

    Why it happens

    Combinational logic depends on current inputs. The same separation helps software: a pure calculation can be checked directly, while storage, time and network access remain explicit at the edges.

    How to work through it

    1. List every input the calculation actually uses, including configuration and time.
    2. Pass those values in and return a result rather than updating hidden globals.
    3. Keep persistence and external calls in a thin surrounding layer.

    The solution

    For example, calculate a charge from supplied usage and a supplied rate table, then save the result separately. A failed calculation can be replayed with those values without rebuilding a database session.

    How to check it

    Call the calculation twice with the same inputs and compare results; then vary one input and check the expected change. Test the surrounding storage path separately.

    Where this stops

    Hardware also contains state, and large input spaces cannot always be enumerated as a truth table. This is a design preference for isolating state, not a claim that every useful function must be pure.

  31. Hardware and EDA

    In EDA, the gap is orchestration, not tools.

    People forget updates and skip steps. Tools should be deterministic recipe runners, with AI only judging, converting or fixing around them.

    How it works

    The problem

    A design flow can have capable tools and still fail because an update was forgotten or a required handoff step was skipped.

    Why it happens

    The missing layer is coordination: declared inputs, ordered work and evidence of completion. A new reasoning model does not by itself make those dependencies reliable.

    How to work through it

    1. Express the repeatable flow as deterministic steps with explicit inputs and outputs.
    2. Record tool versions and the evidence produced by each step.
    3. Use AI for bounded judgment, conversion or diagnosis around that controlled execution.

    The solution

    For example, run a known recipe to produce a timing report, then ask an assistant to explain the failing paths. Keep the execution and report identity independent of the explanation so the result can be reproduced and checked.

    How to check it

    Follow one handoff from input revision to output evidence. Confirm that a missing prerequisite prevents the downstream claim of completion rather than being hidden by a plausible summary.

    Where this stops

    This is an architectural direction from the source. Deterministic orchestration does not remove tool defects, incomplete requirements or the need for engineering judgment.

  32. Hardware and EDA

    AXI bursts must not cross a 4KB boundary.

    The spec forbids it, so an address generator has to split any burst that would run past a 4KB line.

    How it works

    The problem

    An address generator can create a burst that looks contiguous but violates the AXI4 rule against crossing a 4KB boundary.

    Why it happens

    The protocol constrains each addressed burst. A transfer spanning the boundary must be represented by separate legal bursts.

    How to work through it

    1. Compute the remaining bytes before the next 4KB boundary.
    2. Limit the next legal burst to that space and the protocol’s other burst limits.
    3. Continue the remaining transfer in a new burst from the next boundary.

    The solution

    For an aligned illustrative transfer starting at 0x0FF0, only 16 bytes remain before 0x1000. A longer incrementing transfer must finish that burst at the boundary and start another afterward.

    How to check it

    Check addresses just below, exactly at and well away from a 4KB boundary. For each generated burst, verify that its addressed range stays within one permitted region.

    Where this stops

    The example assumes aligned incrementing transfers. Beat size, alignment, burst type and maximum length also affect legal address generation; splitting at 4KB alone is not a complete AXI checker.

  33. Hardware and EDA

    A clock target is a budget.

    At 2.6 GHz the period is about 385 ps, shared by PLL jitter, clock-tree skew, variation and setup time. When one share grows, another must shrink.

    How it works

    The problem

    A clock frequency target can look like one number while hiding several competing demands on the same timing window.

    Why it happens

    At 2.6 GHz, one period is about 385 ps. Jitter, clock-tree skew, variation and setup requirements reduce the time available for useful data-path delay.

    How to work through it

    1. Convert the frequency target into a period.
    2. Write down the timing shares and the assumptions behind them.
    3. Revisit the architecture or target when one share grows beyond its allocation.

    The solution

    For example, if the clock uncertainty increases, that time must come from the remaining path budget unless another share shrinks. Changing the frequency label without revisiting those assumptions leaves the design target inconsistent.

    How to check it

    Check that the timing constraint defines the intended clock period on the right pins, then compare the budget with the relevant timing analysis. The period calculation alone is not timing closure.

    Where this stops

    The shares depend on the technology, path and analysis conditions. This is an illustrative budget from the source, not a claim that all timing terms can always be added without checking the tool’s model.

  34. Hardware and EDA

    Hazards hide between two blocks that each look right.

    Core and accelerator can each pass their own checks while the handoff between them breaks. List the boundary cases early and synchronise retirement across both units.

    How it works

    The problem

    A core and accelerator can each pass unit checks while their combined behavior violates instruction ordering.

    Why it happens

    The risk sits at dispatch, hazard handling and retirement, where both blocks must agree on ownership and visible architectural state.

    How to work through it

    1. List the cross-boundary instruction and hazard cases early.
    2. Define when an operation is accepted, completed and retired.
    3. Exercise both units together with checks on the visible retirement order.

    The solution

    For example, an accelerator operation and a later core operation may complete in a different order. Their handoff must still preserve the architectural ordering the design promises, rather than exposing each block’s local completion order directly.

    How to check it

    Check the combined retirement trace and state changes for the boundary cases. Passing the core’s suite and the accelerator’s suite separately does not establish that the handoff is correct.

    Where this stops

    The exact hazard set depends on the ISA and integration contract. The lesson is to name and verify the seam; it does not supply a universal retirement protocol for every accelerator.

  35. Hardware and EDA

    Constraints and netlists belong in a release area.

    A user's own directory is the least reproducible input. Local config tweaks are fine, but SDC, UPF and netlists need a controlled, traceable source.

    How it works

    The problem

    A flow can use the right tool version and still be hard to reproduce when the exact design inputs and their owner cannot be traced. A personal directory alone does not establish that problem.

    Why it happens

    Untracked edits to timing constraints, power intent or a netlist change the design being built. A path alone does not identify which bytes were used.

    How to work through it

    1. Put shared design inputs under a controlled source or release process.
    2. Record their versions with the run.
    3. Keep local convenience settings separate from the authoritative design inputs.

    The solution

    For example, a user may choose local display settings, but the run should resolve its SDC timing constraints and netlist from a traceable revision. Another person can then obtain the same inputs without copying an entire home directory.

    How to check it

    Inspect the run record and resolve each design input to exact bytes and a responsible owner. A personal checkout can be acceptable when its revision and ownership are traceable; inspect the actual control rule before accepting a path-based warning.

    Where this stops

    Location alone does not establish control. Exploration can start with local files, but comparisons and handoffs need traceable inputs. A shared pathname that can be overwritten is not enough either.

Frontend and design

50 lessons

  1. Frontend and design

    Measure text in a real browser with the real font.

    Character counts guess. The rendered width decides whether a column wraps or overflows.

  2. Frontend and design

    Done means someone looked.

    Code that draws can be valid and still ugly. A figure is finished when the audit passes and a person has seen the render.

    How it works

    The problem

    A drawing can pass its structural checks while remaining hard to read. Valid markup and correctly connected nodes do not guarantee that labels fit or that the eye can follow the intended flow.

    Why it happens

    Automated checks can measure known constraints. A rendered diagram also depends on font metrics, available space and visual relationships that a narrow audit may not represent.

    How to work through it

    1. Run the available audit for geometry, labels and connections, then render the exact candidate at its widest and narrowest supported sizes.
    2. Have a fresh reader inspect the render for overflow, confusing routes, disconnected boxes and misleading emphasis. Fix the observed problem and inspect the new render rather than assuming the code change was enough.

    The solution

    Treat visual inspection as part of completion. The audit supplies repeatable checks; the reader judges whether the image communicates the system it claims to explain.

    How to check it

    Ask the reader to trace one complete path and read its labels at the narrow size. The expected result is a clear route with legible text and no unexplained dead ends.

    Where this stops

    A person's glance is not proof of technical accuracy or accessibility. Keep the source model and text alternative correct, and do not claim someone inspected the candidate unless that review actually happened.

  3. Frontend and design

    Tailwind is styling, not a component library.

    Components come from Radix, shadcn or your own set. Borrow their structure and behaviour, but colour, type and spacing should still come from your tokens.

    How it works

    The problem

    Adding Tailwind does not supply a complete dialog, menu or form component. A styled collection of elements can still lack focus management, keyboard behaviour or a coherent visual system.

    Why it happens

    Tailwind provides styling utilities. Component libraries or local components provide structure and behaviour, while design tokens supply the project's choices for colour, type and spacing.

    How to work through it

    1. Choose the component source separately from the styling layer. Inspect the interaction contract, accessibility behaviour and supported states before copying a visually appealing example.
    2. Map the borrowed component's values to the project's tokens and exercise it in the actual page. Keep its structure where useful without importing a second unrelated theme by accident.

    The solution

    Give each layer one responsibility: reusable behaviour in components, styling through the chosen CSS approach and visual decisions in shared tokens. This makes later changes easier to place.

    How to check it

    For an illustrative dialog check, open it with a keyboard, move focus through it and close it. Also inspect long content, error states and each supported theme.

    Where this stops

    A component library still needs integration testing, and copied source becomes your maintenance responsibility. The historical choice between plain CSS and Tailwind for new work tools remained open; this lesson does not settle it.

  4. Frontend and design

    Check the bundle hash before believing the browser.

    The old design on screen was a cached bundle. Compare the served file to the build before debugging the code.

  5. Frontend and design

    Ask why until you hit a decision.

    A feature is robust when repeated whys end at a recorded decision or policy. If they don't, you've found a decision nobody made.

    How it works

    The problem

    A feature can have detailed implementation notes without a clear reason to exist. When challenged, the explanation may loop through other features instead of reaching a decision someone actually made.

    Why it happens

    An upward why-chain tests justification. Each answer should connect the feature to a requirement, goal or policy, with evidence for that link rather than a plausible story supplied after the fact.

    How to work through it

    1. Choose one feature and ask what need it serves. Follow each answer upward, citing the record that establishes the relationship until you reach an explicit goal, requirement or ruling.
    2. Mark a missing link as a decision gap. Decide whether to remove the feature, narrow it or make the missing policy choice before treating its design as settled.

    The solution

    Use the chain to expose assumptions while they are still cheap to discuss. The useful result is a traceable reason or a named question, not exactly five answers.

    How to check it

    Ask a fresh reader to follow the citations without the author present. They should reach the stated decision and distinguish recorded intent from interpretation.

    Where this stops

    A justified feature can still fail under load or at a trust boundary. This audit checks why it exists; it does not replace tests of whether the implementation works.

  6. Frontend and design

    Find the code point before blaming the font.

    Name the exact glyph (say U+23FA), check which installed fonts cover it, then add a fallback font in your user font folder. No root needed.

    How it works

    The problem

    A terminal symbol can show as a blank even when ordinary text is readable. Before choosing a fallback font, identify the character the terminal is trying to draw.

    Why it happens

    Unicode identifies a character by its code point. Font coverage is a separate question: a familiar monospace font need not contain a glyph for that point.

    How to work through it

    1. Identify the missing character precisely, for example U+23FA. Query installed coverage with fc-list :charset=23fa, using the hexadecimal code point rather than guessing from the symbol’s appearance.
    2. Compare the result with a character that already displays correctly. If no suitable font covers the missing point, choose an appropriate user-local symbols font; then refresh the font cache if needed and restart the terminal.

    The solution

    Use the code point and coverage result to choose a narrow fallback. Keep the main text font when only a few symbols need another source of glyphs.

    How to check it

    Print the exact character in the affected terminal after any change. It should appear at a sensible width beside ordinary text; a coverage listing alone does not prove the terminal selected the font.

    Where this stops

    The font installation was recommended, not confirmed applied. The related lesson on missing glyphs uses this same font-coverage example to distinguish encoding from rendering faults. Their dates are historical log dates, not evidence of two separate failures.

  7. Frontend and design

    Hand-typed API types are a silent contract.

    Rename a backend field and the frontend still compiles. Generate types from one schema so a mismatch goes red.

    How it works

    The problem

    A backend field can be renamed while a frontend with handwritten interfaces still compiles. Each side agrees with its own definition, so the mismatch appears only when real data reaches the page.

    Why it happens

    Types catch inconsistencies inside the contract they are given. A generator cannot repair an API whose published schema says that any response is allowed.

    How to work through it

    1. Declare concrete response models on the backend routes first. Include nested objects, optional values and list items, then inspect the resulting API schema.
    2. Generate the client types from that schema and replace duplicate handwritten interfaces. Add a drift check so changed routes require regenerated artifacts before the build passes.

    The solution

    Move from two manually maintained descriptions to one source of truth in small route groups. Keep any runtime validation needed for responses outside your control.

    How to check it

    For an illustrative trial, rename a field in one typed route and regenerate. A client that still reads the old name should fail its type check.

    Where this stops

    This is a proposed correction where routes lack response models, not proof a migration already happened. Generated types also cannot catch a wrong URL or an incorrect value with the right type.

  8. Frontend and design

    Judge a style from more than one page.

    One screenshot gave the wrong read on my own site's style. Sample widely before calling a pattern a pattern.

    How it works

    The problem

    One screenshot is a weak basis for describing a whole site's style. A landing page may be playful while a learning article uses a much quieter layout.

    Why it happens

    Different page types solve different reading problems. Sampling only the most striking page confuses a local design choice with the rules that hold across the site.

    How to work through it

    1. Inspect several page types, including a dense article, an index and a page with data. Compare their live tokens as well as screenshots so repeated values are visible.
    2. Write down which choices recur and which belong only to a particular page type. Ask the owner to correct the pattern before using it as a design brief.

    The solution

    Build the brief from the range of examples. Keep the common type, colour and spacing rules together, then describe the deliberate exceptions in the context where they apply.

    How to check it

    Try the brief on a page outside the original sample. A useful brief should explain its structure without forcing every screen into the landing page's appearance.

    Where this stops

    The sample still represents one point in the site's history. A redesign, an unfinished page or an experiment may change the pattern, so keep the examples and their dates.

  9. Frontend and design

    Strictness is for the builders, not the customer.

    Strict ledgers and gates are taste for systems people rely on. The customer should feel the reliability, not carry the rules.

    How it works

    The problem

    A reliable system can still be unpleasant if users must learn its internal ledger rules before doing ordinary work. Every extra bookkeeping choice competes with the task they came to complete.

    Why it happens

    The engine needs strict records so changes have an owner, a reason and a traceable effect. Those requirements describe how the system preserves trust; they need not become manual chores on every screen.

    How to work through it

    1. Start from the user's action and identify which facts the system can collect itself. Record the change as an event and compute which dependent work becomes stale.
    2. Expose the decision that needs human judgement, such as rerun, defer or waive. Give its cost and consequence rather than asking the user to reconstruct the dependency machinery.

    The solution

    Keep strictness in the underlying mechanism and make the interface explain meaningful choices. A simple action can still create a complete audit record.

    How to check it

    Walk an ordinary change through the interface, then inspect its record. The user should complete the task while the owner, reason and affected work remain recoverable.

    Where this stops

    Some regulated or high-risk actions need explicit acknowledgement. Hiding implementation detail must never hide a consequence or turn a proposal into an automatic expensive rerun.

  10. Frontend and design

    SVG is the wrong tool for painterly looks.

    Watercolour or oil textures need raster generation. Vectors suit line and low-poly diagrams, and they diff and review like code.

    How it works

    The problem

    A painterly brief can become a long fight with a vector drawing tool. Smooth paths and editable shapes are useful, but they do not naturally provide the texture requested by a watercolour image.

    Why it happens

    SVG represents shapes, lines and text as structured drawing instructions. Raster images store pixels, which suit irregular marks and soft texture without requiring each mark to become an editable object.

    How to work through it

    1. Decide which property matters most: diagram structure and editable labels, or the appearance of a painted surface. Choose the output format before refining the image prompt.
    2. Use SVG for line diagrams and simple geometric artwork. For a painterly illustration, generate a raster image at the intended display size and keep important labels separate where possible.

    The solution

    Match the medium to the job so effort goes into meaning and composition. A diagram benefits from visible structure; an atmospheric image benefits from texture.

    How to check it

    Inspect the result at its actual page size. Check that diagram labels remain legible, or that the raster texture survives without distracting from the surrounding text.

    Where this stops

    This is a practical default rather than a hard format limit. SVG filters and mixed media can imitate texture, but they add complexity that a simple illustration may not need.

  11. Frontend and design

    Test the response shape with the route.

    A route that returns 200 with the wrong shape passes a status check. Checking the shape is the same test, extended.

    How it works

    The problem

    A route can return HTTP 200 and still break its client. The request succeeded, but a missing field, changed type or unexpected null may leave a blank column or a runtime error.

    Why it happens

    A status check covers transport success. The response contract also includes field names, nesting, lists and whether a value may be absent or null.

    How to work through it

    1. Extend a route test to validate the returned body against the declared response schema. Include a representative object and empty or nullable cases that clients actually handle.
    2. Generate client types from the same contract where possible. Keep generation and schema-drift checks in the build so a stale committed file fails visibly.

    The solution

    Use one contract to connect server output, tests and client expectations. This avoids maintaining a second handwritten shape that can drift in exactly the same way as the application.

    How to check it

    As an illustrative check, rename a required response field in a scratch change. The contract or client check should fail even though the endpoint still returns 200.

    Where this stops

    Shape checks do not prove that a value is correct or that the live service returns the expected data. They complement semantic assertions and a few real integration checks.

  12. Frontend and design

    An alarm that's always lit gets ignored.

    Highlights are alarms. Keep them under a rate ceiling, or a permanently lit row trains people to look away.

    How it works

    The problem

    A waiting-job highlight can become part of the dashboard's normal background. When it is lit most of the time, it stops helping someone decide what needs attention.

    Why it happens

    An alarm threshold defines both a severity boundary and an interruption rate. Choosing only by elapsed wait ignores how often ordinary workload crosses that boundary.

    How to work through it

    1. Choose a representative time window and count the observation ticks on which the highlight would be active. Keep the denominator visible rather than counting only affected jobs.
    2. Compare candidate thresholds against an agreed rate ceiling. Separate exceptional workload classes to see whether one class explains most of the noise.

    The solution

    Use the measured rates to make an explicit policy decision. Keep a failed tripwire visible while the threshold is undecided instead of quietly weakening the test.

    How to check it

    Replay the same window after a proposed change. The expected check is that ordinary periods stay below the ceiling while genuinely concerning waits remain visible.

    Where this stops

    A low firing rate alone does not make an alarm useful. Some frequent conditions really need action, and the correct ceiling depends on the team's response capacity.

  13. Frontend and design

    One CSS system per project.

    Plain CSS on custom-property tokens, and no utility framework beside it. Two systems drift, and nothing tells a new page which one to use.

    How it works

    The problem

    A new page can look reasonable by itself while drifting from the rest of a site. This becomes easier when two styling systems offer different answers for the same colour, gap or component.

    Why it happens

    Custom properties let many rules share one named value. A second utility or theme system creates another source of defaults, so a token change may reach only part of the interface.

    How to work through it

    1. Find the project's existing tokens and component rules before styling the page. Use those names for colour, type, spacing and theme changes.
    2. Add a missing shared token in the existing system, then use it where needed. Avoid introducing another framework merely to express a few local declarations.

    The solution

    Give contributors one clear route from a design choice to the CSS that implements it. A shared token should change every place that represents that same choice.

    How to check it

    Change a token in a local preview and inspect old and new pages in each supported theme. Look for raw values or alternate defaults that prevented the change from carrying through.

    Where this stops

    This is a project convention, not a rule against utility CSS everywhere. A planned migration can use two systems temporarily if ownership and the removal path are clear.

  14. Frontend and design

    When a layout choice is unclear, ask what the page is for.

    Restate the decision in terms of what the reader came to do. The answer usually picks the option.

    How it works

    The problem

    A layout debate can drift into personal preference when neither option is tied to the reader's task.

    Why it happens

    Different placements serve different actions. Without deciding what the reader needs first, visual comparison has no shared basis for choosing between otherwise reasonable options.

    How to work through it

    1. State the reader's immediate question or action.
    2. Compare how each layout helps complete it.
    3. Use a small realistic example to inspect the trade-off and record the chosen reason.

    The solution

    Illustrative: a history control needed for frequent comparison may belong near the comparison figures, while a rarely used configuration control may suit a quieter location. The task explains the placement.

    How to check it

    Ask a reader to complete the named task in each candidate layout. Observe whether the needed information and controls are found in the intended order.

    Where this stops

    Several tasks can be equally important. If the task evidence does not distinguish the options, keep the choice reversible and identify what later observation would justify changing it.

  15. Frontend and design

    A dashboard page leads with its answer.

    One sentence first, then figures, then tables. Make readers dig and they will quote the wrong number.

    How it works

    The problem

    A dashboard can make readers choose a number before they understand what question it answers.

    Why it happens

    A table gives many facts equal visual weight. Without a plain-language finding and scope first, a reader may quote a nearby number that measures a different population or period.

    How to work through it

    1. Open the page with its main finding and relevant scope.
    2. Place the supporting figures next.
    3. Use detailed tables as the evidence readers can inspect after understanding the claim.

    The solution

    Illustrative: a sentence naming the affected job population gives a waste figure context before the reader reaches the per-job table. The detail then supports the answer instead of competing to define it.

    How to check it

    Ask a reader to state the page's answer, time window and population after a brief scan. Their statement should match the figures and the table's filters.

    Where this stops

    The opening sentence must be generated from the same current data and filters. A confident summary that ignores stale or incomplete data is more misleading than a dense table.

  16. Frontend and design

    Check every width in a real browser.

    Phone-width overflow only showed up in live viewport checks. Unit tests don't render anything.

    How it works

    The problem

    A layout can pass component tests while still overflowing the actual phone viewport.

    Why it happens

    Tests that inspect component logic do not necessarily run browser layout with real fonts and content. Long labels, minimum widths and nested containers can exceed the available space only when rendered together.

    How to work through it

    1. Open the built interface in a real browser.
    2. Check representative pages at the supported widths and themes.
    3. Inspect horizontal overflow and exercise the controls that change content width.

    The solution

    Illustrative: a table inside a correctly sized panel can still force the whole page wider because one nested element has a fixed minimum width. The browser exposes that relationship.

    How to check it

    At a phone-width viewport, compare document scroll width with viewport width and inspect any offending element. Important controls and required data should remain reachable after the fix.

    Where this stops

    Some tests do render in a browser; the original lesson's shorthand does not describe every unit-testing tool. A screenshot alone also cannot prove keyboard access or correct interactions.

  17. Frontend and design

    If the UI doesn't show it, it wasn't computed.

    Metrics the database worked out and the page threw away were wasted effort. A lot of value was already there, unshown.

    How it works

    The problem

    A useful metric can exist in the database and still be unavailable to the person making the decision.

    Why it happens

    A response field that is dropped by the UI or export has no visible gap where it should have been. Backend computation alone does not establish that the product exposes its result.

    How to work through it

    1. Inventory the fields the backend already returns.
    2. Map each decision-relevant metric to a visible explanation or export field.
    3. Check coverage in both directions so removed and newly added fields are noticed.

    The solution

    Illustrative: adding a computed waste measure to SQL is incomplete if neither the page nor its CSV includes it. Surfacing the existing field may solve the need without another query.

    How to check it

    Compare the actual API response with the displayed and exported field sets. Required metrics should appear with units and scope, and obsolete fields should not linger silently.

    Where this stops

    Not every internal field belongs in the UI. Select fields by the user's task and privacy needs; coverage should guard the agreed public contract rather than expose the database wholesale.

  18. Frontend and design

    Secrets go in the URL fragment, not the query.

    The #fragment never leaves the browser in requests or server logs. The query string ends up in both.

    How it works

    The problem

    A browser link can leak a secret into server request logs if the secret is placed in its query string.

    Why it happens

    The URL fragment is handled client-side and is not included in the HTTP request target. Query parameters travel to the server, where normal access logging can retain them.

    How to work through it

    1. Keep the secret out of the request query.
    2. Read the fragment only in the intended client code.
    3. Remove it from the visible address after use and avoid forwarding it into another request or log.

    The solution

    Illustrative: a remote-control page can receive a one-time client value after # without the initial page request carrying that value to its host.

    How to check it

    Inspect the actual network request and server-side request logs in a disposable flow. The fragment value should be absent unless application code explicitly sends it later.

    Where this stops

    Fragments are still accessible to page scripts, extensions and anyone who sees the link. This limits one transport leak; it is not a complete secret-storage or authentication design.

  19. Frontend and design

    Accept what you print.

    The UI showed durations like 1.5h and the filter rejected decimals. Whatever the app displays, its inputs should take back.

  20. Frontend and design

    astro preview ignores your Vite config.

    It builds Vite with configFile:false, so proxy and allowedHosts settings silently drop. Use the Astro server block, or astro dev.

    How it works

    The problem

    An Astro page can remain blocked by hostname or lose its API proxy even after the Vite preview settings look correct.

    Why it happens

    The inspected preview path constructs its own Vite configuration with configFile:false. Settings placed in vite.preview therefore did not govern that server in the reported setup.

    How to work through it

    1. Identify whether the app runs through Astro dev or preview.
    2. Place host configuration in Astro's supported server block.
    3. Use the mode that supports the needed proxy and test the full browser-to-API path.

    The solution

    Illustrative: a page that loads by loopback address may still fail through its actual hostname. Test the intended Host header as well as a relative API request.

    How to check it

    Expect both the real hostname request and the proxied API request to reach their intended handlers. A listening port alone does not establish either result.

    Where this stops

    This describes the inspected Astro behavior and configuration. Check the installed version's documented options before assuming every preview release supports the same settings.

  21. Frontend and design

    Number(null) is 0.

    A missing value rendered as 0.0%. Keep null and zero apart all the way to the pixel.

    How it works

    The problem

    A missing percentage can look like a measured zero when the UI coerces it into a number.

    Why it happens

    JavaScript converts Number(null) to zero. Once that happens, a formatter cannot distinguish absent evidence from a real zero-percent result.

    How to work through it

    1. Preserve null in the response type and conversion helper.
    2. Only format a number after confirming it exists.
    3. Choose a clear missing-value representation for the table, chart and export.

    The solution

    Illustrative: null becomes a dash or an explicit unknown label, while numeric 0 becomes 0.0%. The two displays tell the reader whether a measurement exists.

    How to check it

    Render null, zero and a positive percentage through the real formatter. Expect distinct missing and zero states, and check that CSV conversion does not merge them again.

    Where this stops

    A dash needs a clear meaning. Missing, not applicable and failed to fetch may require different states; one fallback symbol should not hide a request failure.

  22. Frontend and design

    Meet a brand point size with optical size, not scaling.

    A spec like 'Inter 18pt' is honoured through the font's optical-size axis and leading, not by literally blowing the text up.

    How it works

    The problem

    A brand typography instruction can be translated into an oversized web layout if its intent is copied without considering the screen.

    Why it happens

    Font size, optical-size design and line spacing are separate controls. A variable font's optical-size axis adjusts letterforms; leading adjusts room between lines. Neither is identical to a literal point-size conversion.

    How to work through it

    1. Identify which parts of the brand requirement are mandatory.
    2. Choose the supported font settings and line spacing for the reading role.
    3. Review the rendered result and resolve any literal-size requirement with the design owner.

    The solution

    Illustrative: a dense interface can use the chosen Inter letterforms and generous leading while keeping a practical screen size. Document that interpretation instead of silently claiming numerical equivalence.

    How to check it

    Inspect actual loaded font settings, line breaks and reading comfort at normal zoom. Confirm that the result matches the agreed interpretation of the spec.

    Where this stops

    Optical size does not make small text physically 18 points. If the requirement is a literal size or an accessibility minimum, follow it rather than substituting a different axis.

  23. Frontend and design

    TypeScript types don't cross the network.

    A client compiled cleanly while calling four endpoints the API never defined. Only a test against the server's real routes catches that.

    How it works

    The problem

    A frontend can pass its type check while sending requests to routes the server does not provide.

    Why it happens

    A URL written as a string is valid TypeScript regardless of whether a matching server route exists. Locally consistent client types cannot establish a remote contract.

    How to work through it

    1. Extract the routes actually called by the client.
    2. Compare their methods and paths with the server's registered routes.
    3. Add a live smoke check for important deployed paths after restarting the server.

    The solution

    Illustrative: a perfectly typed function calling a nonexistent summary route still compiles. A route-contract check fails before the user reaches that page.

    How to check it

    Deliberately change a client path in a disposable check. The contract test should fail, and the unchanged deployed client should reach its real server route.

    Where this stops

    Route existence does not prove response shape, filter behavior or correct values. Generated schemas and runtime integration checks cover different parts of the contract.

  24. Frontend and design

    Tokens, never hex.

    Every colour, gap and radius comes from a token. One place to change the theme, and no one-off values creeping in.

    How it works

    The problem

    A theme change becomes a series of local fixes when each component owns its own colour and spacing values.

    Why it happens

    Hardcoded values copy a design decision into many places. Shared tokens give that decision one owner, so components can follow the same theme without independently choosing each number.

    How to work through it

    1. Define tokens for semantic colours, spacing and radii.
    2. Replace component literals with the appropriate token.
    3. Check that both built themes contain every token the UI reads.

    The solution

    Illustrative: changing the panel background token should update every panel assigned that role. A single hardcoded panel remains visibly different and reveals where ownership drifted.

    How to check it

    Inspect representative components in each supported theme and search for unexplained literals. Required runtime tokens should exist in the built stylesheet as well as in source.

    Where this stops

    Tokens do not guarantee contrast or a good hierarchy. Several distinct roles can need distinct tokens even when they currently share a value; avoid coupling them merely because the numbers match.

  25. Frontend and design

    An accent is a spotlight, not a paint.

    One loud colour, one focal point per view. Use it everywhere and nothing stands out.

    How it works

    The problem

    A bright accent loses its purpose when every control and panel uses it.

    Why it happens

    Visual emphasis depends on contrast with quieter surroundings. Repeating the loud treatment across the whole view makes routine content compete with the action or finding the reader should notice first.

    How to work through it

    1. Choose the main task or finding for the view.
    2. Reserve the strongest accent treatment for that focal element.
    3. Use neutral text, spacing and weaker lines for supporting structure.

    The solution

    Illustrative: one highlighted action beside neutral secondary controls gives the reader a starting point. Filling all the controls with the same bright colour removes that distinction.

    How to check it

    Review the complete viewport with real content. A reader should be able to identify the intended focal element before reading every label, while text remains legible on accented surfaces.

    Where this stops

    Status information can require several colours. Keep those meanings stable and provide text or shape cues as well; one accent is a hierarchy rule, not a reason to erase state.

  26. Frontend and design

    Never trade information for looks.

    Wide tables get merged cells and an 'all columns' view, not hidden data. A pretty table that drops a column is a wrong table.

    How it works

    The problem

    A table redesign can look cleaner by removing fields that a reader still needs to answer the question.

    Why it happens

    Layout pressure is a presentation problem. Hiding a data column to fit the screen changes what can be learned from the table and can make an incomplete answer appear complete.

    How to work through it

    1. List the information each task requires before redesigning.
    2. Group related values or merge suitable presentation cells without discarding their meaning.
    3. Provide a clear route to all columns and verify narrow-screen access.

    The solution

    Illustrative: a compact view may combine requested and used memory in one cell while an all-columns view keeps both separately available. The relationship stays visible without deleting either measurement.

    How to check it

    Compare the redesigned table against the original field inventory. Every required value should be accessible, correctly labelled and usable at a phone-width viewport.

    Where this stops

    Not every field belongs in the default view. Progressive disclosure is useful when access is clear; silently removing required information is the failure this lesson addresses.

  27. Frontend and design

    Safe to change depends on who reads it, not who owns it.

    Owning a file doesn't make editing it safe. Count its consumers; a file with many readers needs care whoever owns it.

    How it works

    The problem

    Editing a file can affect more people than its ownership suggests.

    Why it happens

    A file's owner controls its permissions, but its readers determine the effect of a change. Shared constraints can influence several flows even when the file lives in one user's area.

    How to work through it

    1. Identify the actual consumers and the role of the file.
    2. Separate provenance questions from assumptions based on its name or directory.
    3. Review downstream effects before changing an input used by several flows.

    The solution

    Illustrative: changing one timing constraint may alter several analyses that consume it. The useful question is which analyses read that value, rather than whether the editor owns the file.

    How to check it

    Trace representative consumers to the exact input they load. Review the proposed change against those consumers and keep an unconfirmed classification marked as unconfirmed.

    Where this stops

    A suggestive filename does not establish that a file is a signoff input. If consumers are unknown, record the gap rather than claiming either a safe edit or a confirmed unsafe dependency.

  28. Frontend and design

    Rank with weight and space, not size.

    Small, dense type reads calmer. Weight, case and spacing do the ranking without shouting.

    How it works

    The problem

    A dashboard can become harder to scan when every level of importance is expressed by larger text.

    Why it happens

    Large headings consume space and compete with the figures. Weight, spacing and case can establish structure while keeping the page dense enough to compare related information.

    How to work through it

    1. Define a small shared type scale.
    2. Use weight and spacing to separate titles, sections and labels.
    3. Review the result with realistic data rather than an empty mockup.

    The solution

    Illustrative: a modest semibold section title followed by a clear gap can distinguish a group without pushing its key values below the fold. Keep the main finding visually distinct from routine labels.

    How to check it

    Scan the page at normal viewing size and browser zoom. The reader should identify the main finding and section boundaries without losing access to small text.

    Where this stops

    Density is not permission to reduce legibility. Low contrast, tiny labels or a reader's accessibility needs can justify larger text even when the preferred visual style is compact.

  29. Frontend and design

    Monospace for machines, a reading face for prose.

    Mono for labels, ids and code. A proportional face for long text. Pick type by role, not by mood.

    How it works

    The problem

    A page can look consistent while making long explanations tiring to read and identifiers awkward to compare.

    Why it happens

    Monospace gives every character the same width, which helps machine strings line up. Proportional type gives prose a more natural reading rhythm; using either everywhere ignores its role.

    How to work through it

    1. Classify text as prose, labels, identifiers or code.
    2. Assign a small font set through shared tokens.
    3. Read a realistic long passage and scan representative machine values before finalizing the roles.

    The solution

    Illustrative: keep a table's job identifiers in mono while using a proportional face for the explanation above it. The contrast communicates what to scan and what to read without adding another colour.

    How to check it

    Inspect the page with real content, long identifiers and fallback fonts. Prose should remain comfortable, and machine values should remain distinguishable without clipping.

    Where this stops

    This is a design choice tied to purpose, not a universal ban on monospace prose. Size, contrast, line length and font loading still matter whichever family is chosen.

  30. Frontend and design

    Code goes in a terminal window.

    On my pages, prose gets paper and ink, and code gets a terminal frame. The frame tells you what kind of text you're reading.

    How it works

    The problem

    A reading page can make prose, shell commands and output look too similar, leaving readers unsure which text they should execute.

    Why it happens

    Visual framing signals the role of a passage before its details are read. In this site’s design, a terminal frame gives code a consistent place inside the paper-and-ink page.

    How to work through it

    1. Render code blocks through the shared terminal-style component while leaving the surrounding explanation on the reading surface.
    2. Label commands and expected output clearly inside that frame, preserving whitespace and providing accessible text rather than a screenshot of code.

    The solution

    For a two-command example, keep the commands copyable and distinguish any illustrative prompt marker from the command itself. The decorative frame should support that reading, not add work to copying.

    How to check it

    Inspect a long line, a short snippet and a command/output pair in both themes and at narrow widths. Confirm the text remains readable, selectable and correctly ordered.

    Where this stops

    This is a personal page convention, not a rule for all technical writing. A terminal appearance must not imply that an illustrative command was actually run or that all framed code is shell syntax.

  31. Frontend and design

    Show the lines.

    I like drawings where you can see the strokes: line art, low-poly, diagrams that look drawn by hand. A diagram that looks drawn reads as a diagram.

    How it works

    The problem

    A diagram can become harder to read when visual polish hides the strokes, boundaries and connections that explain its structure.

    Why it happens

    The drawing’s job is to make a system legible. Rendering fidelity can compete with that job when it gives surfaces more weight than the relationships the reader needs.

    How to work through it

    1. Choose a line-based style for diagrams and give the important paths a clear, consistent stroke treatment.
    2. Use labels and a small accent to explain meaning, then remove shading or detail that does not help a reader follow the structure.

    The solution

    For a block diagram, let the boxes, ports and lines remain visible as constructed marks. A code-generated SVG can make that treatment repeatable while keeping the drawing editable.

    How to check it

    Ask a reader to trace one input through the drawing and identify its output. Check the same route at the size where the diagram will actually be read.

    Where this stops

    This is a design preference, not a rule for every image. The source distinguishes line-based diagrams from painterly raster imagery; a photograph or texture has a different purpose.

  32. Frontend and design

    Light themes should be paper, not white.

    Pure white on a screen glares. A cream background with dark ink reads like a page.

    How it works

    The problem

    A light reading page can feel harsh when its largest surface is pure white, especially beside strong dark text and bright accents.

    Why it happens

    The background is part of the reading experience, not unused space. On a long page, its colour is present behind almost every line.

    How to work through it

    1. Set a shared light-theme paper token to a restrained cream tone and pair it with a dark ink token.
    2. Apply those tokens consistently to the page and reading surfaces, then check text, links and code frames against the actual backgrounds.

    The solution

    Use the paper-and-ink palette for long reading in this design system. Keep one accent for emphasis rather than adding several saturated surfaces that compete with the prose.

    How to check it

    Inspect a full article, not just a colour swatch. Check contrast, long paragraphs and embedded figures in both themes, and ask whether any component still introduces an unintended white panel.

    Where this stops

    This is a stated visual preference, not a universal claim that cream is more accessible for everyone. The chosen colours still need contrast checks and support for reader preferences.

  33. Frontend and design

    Derive graph edges. Never draw them.

    A graph view is a projection of a store. Hand-drawn edges go stale; edges aggregated from the data can't.

    How it works

    The problem

    A graph can look convincing while missing the relationships needed to understand a change’s downstream effects.

    Why it happens

    If someone keeps the picture’s edges separately from the underlying store, each edit creates a chance for the two descriptions to disagree.

    How to work through it

    1. Store relationship identity and meaning with the underlying entities, then generate the displayed edges from those records.
    2. Keep layout choices separate from relationship data, and make missing endpoints or unsupported relationship types visible.

    The solution

    For a dependency view, changing a stored dependency should change the next rendered graph without someone manually redrawing a line. Different zoom levels can summarize the same relationships.

    How to check it

    Compare rendered edge identities with a small known store and test an added, removed and broken relationship. Check propagation behaviour separately if those edges also drive stale-state calculation.

    Where this stops

    Derived views can still be stale when caches, queries or source records are wrong. The original card’s “can’t” is too absolute; derivation removes manual duplication, not every failure mode.

  34. Frontend and design

    CORS lives in the browser, so curl proves nothing.

    curl and server-to-server calls ignore CORS. And an error response without CORS headers shows up in the console as a CORS failure.

    How it works

    The problem

    An API request can work in curl and still be blocked when a web page makes the same request. The server being reachable does not establish browser access.

    Why it happens

    The browser enforces cross-origin resource sharing, or CORS. Command-line clients do not apply that browser policy, and a server error without CORS headers can hide its real response from page code.

    How to work through it

    1. Reproduce from the actual page origin in a browser and inspect both the request and any preflight request.
    2. Check the allowed origin, method and headers on successful responses and on errors; inspect server logs when the browser cannot expose the response.

    The solution

    Test the path users take. Use curl to isolate transport or server behaviour, then use the browser to establish whether the intended origin can read the result.

    How to check it

    Expect a permitted page request to expose its response to page code. Also trigger a controlled server error and confirm it remains diagnosable through the intended CORS policy.

    Where this stops

    A browser failure may also involve authentication, mixed content or networking. CORS headers do not grant server-side authorization and should not replace it.

  35. Frontend and design

    Colour alone can't carry state.

    A red dot means nothing to a colour-blind reader or on a grey printout. Add a shape, a pattern or a word.

    How it works

    The problem

    A status display loses meaning when a reader cannot distinguish its colours, or when the page is printed in grey.

    Why it happens

    Colour conveys a difference without necessarily naming it. Even readers who see every colour still need to know whether a particular dot means queued, running or failed.

    How to work through it

    1. Give each state a visible word or a distinct shape, then use colour as an additional cue.
    2. Put the legend near the display and check it against every state the data can produce, including missing or unknown data.

    The solution

    For example, show “failed” beside an error mark rather than expecting a red circle to explain itself. Keep the label attached to the item, so the reader need not repeatedly consult a distant key.

    How to check it

    View the screen in greyscale and ask someone to identify each state without using its colour. Check that text and marks also remain legible at the intended size.

    Where this stops

    This is one accessibility check, not a complete accessibility review. Contrast, keyboard use and assistive technology still need their own checks.

  36. Frontend and design

    Taste stays human.

    AI implements a design well and invents one badly. I set the direction and the templates; the agent fills them in.

    How it works

    The problem

    An agent can produce a technically complete screen that still feels wrong. Correct layout code does not decide which visual direction, level of detail or emphasis suits the person using it.

    Why it happens

    Implementation and judgment are different jobs. Without a clear reference, an agent fills an open design brief with its own defaults, and each local choice can pull the page in another direction.

    How to work through it

    1. Choose the purpose of the page and the few things its reader should notice first. Bring examples that show the intended direction.
    2. Set the reusable structure, tokens and templates before asking the agent to fill in the content or implement the view.
    3. Look at the rendered result and give concrete corrections: what draws too much attention, what is hard to read and which reference it should move toward.

    The solution

    Keep the direction and final judgment with the human. Use the agent to execute the chosen system consistently and to make alternatives cheap enough to compare.

    How to check it

    Compare the actual page with the references at the sizes people will use. Passing code checks should sit alongside that visual review, rather than standing in for it.

    Where this stops

    This is my working preference, not a measured limit on every model or a claim that all human design choices are good.

  37. Frontend and design

    Hover-only help doesn't exist on a phone.

    Tooltips need a pointer. Put the hint in the placeholder or a help line people can reach on any screen.

    How it works

    The problem

    Help available only on hover can leave touchscreen users and keyboard users without the instruction needed to operate a control.

    Why it happens

    A hover trigger assumes a pointing device and an interaction people may never discover. The hint can be present in the implementation yet absent from the user’s working path.

    How to work through it

    1. Put essential instructions in visible text associated with the control.
    2. Use a reachable help action for longer explanations and examples.
    3. Check keyboard, touch and assistive-technology paths alongside pointer use.

    The solution

    For example, a filter box can show a short help line explaining AND, OR and NOT. A longer guide can provide examples without making the user discover an invisible hover target first.

    How to check it

    Try the task without a mouse and on a narrow touch layout. Confirm that the user can find the rule before submitting an invalid query, and that help remains associated with its control.

    Where this stops

    Placeholder text disappears during entry and should not replace a persistent label or essential instructions. Tooltips remain useful for supplementary detail when the core path is already understandable.

  38. Frontend and design

    Motion should scale with the page's purpose.

    A showcase page can have theatre. A page someone works in for hours gets one short ease and nothing else. Every page needs a reduced-motion fallback.

    How it works

    The problem

    Motion that helps introduce a portfolio can become distracting when repeated throughout a page someone uses for hours.

    Why it happens

    The purpose and duration of use change the cost of animation. A working interface needs stable reading and interaction targets, while a short showcase may reasonably spend more attention on presentation.

    How to work through it

    1. Classify the page by how people use it and for how long.
    2. Keep work-page motion brief and tied to a useful state change.
    3. Provide a reduced-motion path for every animated experience.

    The solution

    For example, a short easing transition can make a control’s state change clear without keeping the rest of the dashboard in motion. A continuously moving backdrop adds no information to that task.

    How to check it

    Use the page for a representative work session, then enable the system’s reduced-motion setting and check every animated element. Inspect whether content remains visible and interactions still make sense.

    Where this stops

    These are recorded design preferences, not universal duration limits. Accessibility, performance and user feedback still determine whether a particular animation earns its place.

  39. Frontend and design

    Deploy the server before the client.

    A new client sending requests the old server rejects breaks for everyone during the rollout. Server first, then client.

    How it works

    The problem

    A client update can break working users when it begins sending a request format the still-running server does not accept.

    Why it happens

    A rollout has an interval in which old and new versions coexist. If the new client requires new server behavior immediately, deploying it first creates an avoidable incompatible pair.

    How to work through it

    1. Make the server accept both the old and new request forms during transition.
    2. Deploy and restart every server process that consumes the changed contract.
    3. Verify the new server before releasing the client that depends on it.

    The solution

    For example, a richer filter grammar should be accepted by the API and any background consumer before the interface starts sending it. Updating files alone does not update a process that imported the old parser.

    How to check it

    Exercise an old-client request and a new-client request against the deployed server, then check the client path end to end. Include a rollback rehearsal when the change is significant.

    Where this stops

    Server-first works when the server change is backward compatible. Removing fields or changing their meaning needs a wider migration plan rather than relying on rollout order alone.

  40. Frontend and design

    One shared component can bloat a bundle.

    Sharing a filter control pulled a whole page's code into another chunk, which grew six-fold. Check chunk sizes when you share components.

    How it works

    The problem

    Reusing a small interface control can unexpectedly increase the amount of JavaScript another page must load.

    Why it happens

    A component brings its imports with it. If a reusable filter depends on page-level modules, the bundler may pull those modules and their dependencies into the consuming page’s chunk.

    How to work through it

    1. Compare the consuming chunk before and after the shared import.
    2. Inspect the dependency graph to find the large transitive imports.
    3. Move the small shared contract and control into modules that do not depend on an entire page.

    The solution

    Keep shared UI code close to the data types and behavior it actually needs. In an illustrative split, a filter accepts values and a change callback; it does not import the jobs page to obtain them.

    How to check it

    Build the affected pages and inspect emitted chunk sizes and their imports. Then exercise the control in both pages to confirm that the smaller boundary preserved behavior.

    Where this stops

    Bundle size is one cost, and an intentional shared dependency may be worthwhile. The source reports a large chunk increase; this rewrite does not claim it has been removed.

  41. Frontend and design

    NFKC is for comparing, not storing.

    It folds ligatures, superscripts and full-width forms, which is great for matching extracted text and lossy for content you keep.

    How it works

    The problem

    A text-comparison check can reject equivalent-looking extracted text, or damage stored content while trying to avoid that false alarm.

    Why it happens

    NFKC is Unicode compatibility normalization. It folds some distinct characters into common forms, including ligatures, full-width characters and superscripts. That helps matching but loses distinctions.

    How to work through it

    1. Keep the original extracted and source text unchanged.
    2. Create a separate normalized view for the comparison.
    3. Define which differences the check is allowed to ignore, then retain raw mismatches for inspection.

    The solution

    For example, a ligature and its ordinary letters can compare equal after normalization. A superscript becoming an ordinary digit may be acceptable for one search index but unacceptable for preserving a technical expression.

    How to check it

    Use paired fixtures: one with an expected compatibility difference and another where the character distinction matters. The comparison should ignore only the differences its contract permits.

    Where this stops

    NFKC does not fix every extraction issue, such as hyphenation or whitespace. Applying the same broad fold to both sides can also hide real loss, so a normalized pass is not a losslessness proof.

  42. Frontend and design

    Your faintest text token is where contrast fails.

    A muted ink at about 3.3:1 sits below the 4.5:1 that small text needs. Check the dimmest token, not just the body colour.

    How it works

    The problem

    A page can have readable body text while its small comments and secondary labels fail contrast checks.

    Why it happens

    The faintest text token often receives less attention than the main ink colour. Its foreground/background pair still needs enough contrast for the text’s size and role.

    How to work through it

    1. List every text token and its actual backgrounds.
    2. Measure the weakest pairs, including secondary labels and states.
    3. Adjust the token or its use rather than fixing only one visible instance.

    The solution

    A muted colour around 3.3:1 falls below the usual 4.5:1 requirement for normal-size text. Darkening or lightening the shared token, as appropriate for the background, can fix the same problem across many components.

    How to check it

    Check the rendered pairs in each supported theme and state. Include the smallest secondary text; a passing body colour does not establish that the page’s whole text system passes.

    Where this stops

    Contrast thresholds differ for large text and some exempt content. A numerical pass also does not fix tiny type, poor spacing or colour-only status cues, so inspect the reading experience too.

  43. Frontend and design

    A drawing should look like someone drew it.

    Thin ink lines, visible characters and one filled accent, never a polished photo-sharp finish. Code-drawn SVG keeps how the picture was made on show.

    How it works

    The problem

    A technical illustration can become so polished that its construction is hidden and the drawing stops feeling like part of the site’s diagram language.

    Why it happens

    A useful diagram style keeps visible lines and characters, with a small accent rather than a photo-sharp finish. The source describes this as an inferred visual preference rather than a separately confirmed personal rule.

    How to work through it

    1. Start with the shapes and relationships the drawing must communicate.
    2. Draw them in SVG with thin ink lines and visible construction.
    3. Use one small filled accent to direct attention, keeping labels readable.

    The solution

    For a block illustration, let the outlines and connectors explain the system. An accent can mark the active block without filling every shape or adding a new colour for each part.

    How to check it

    View the drawing at its actual page size. Check that the lines, characters and one point of emphasis survive scaling and that the intended relationship can still be read.

    Where this stops

    This is a design preference, not a universal quality rule. A photograph or dense data figure may serve another purpose better, and visible construction should never make the subject harder to understand.

  44. Frontend and design

    The hard part of a chat UI isn't the screen.

    Drawing a chat window is easy. Owning sign-in and conversation storage is the real work, and the real risk, of a custom front end.

    How it works

    The problem

    A chat screen can be built quickly while the shared product remains unready. Sign-in, conversation access and retained history carry most of the responsibility.

    Why it happens

    A custom frontend becomes an owner of identity and stored conversations, not merely a renderer for model replies.

    How to work through it

    1. Decide who authenticates the user.
    2. Define who may read, retain and delete each conversation.
    3. Keep the model-facing adapter narrow so the screen can be replaced without moving the security boundary.

    The solution

    For a local single-user prototype, a lightweight UI may be enough. Before sharing it, decide how one user’s saved chat is prevented from appearing in another user’s session and which service owns that check.

    How to check it

    Test two separate identities and their conversation histories through the actual sign-in path. A working model reply does not establish that history storage and access controls are correct.

    Where this stops

    Managed frontends can reduce the work but bring their own integration and data-handling contracts. Compare those responsibilities before choosing on screen-building effort alone.

  45. Frontend and design

    A test that scrolls first can't see the fold.

    Scrolling the element into view hides what a real first visit shows. Test at the real screen size, from the top.

    How it works

    The problem

    A browser test can make a control visible before taking its screenshot, hiding the fact that a new visitor would not see it on the initial screen.

    Why it happens

    Automatic scrolling changes the layout state being tested. A check after scroll answers a different question from a first-visit visibility check.

    How to work through it

    1. Set the viewport to the intended user screen size.
    2. Open the page at the top without scrolling to the target.
    3. Inspect the first screen before testing later interactions or sections.

    The solution

    For a primary action expected above the fold, check its position in the initial viewport. A locator that scrolls it into view can still be useful for clicking, but it cannot establish initial visibility.

    How to check it

    Capture the first viewport and check clipping, overlap and the action’s bounds. Repeat at the actual small screen sizes the page supports, not only a large standard screenshot size.

    Where this stops

    Some content intentionally belongs below the fold. Define which elements must be visible initially, and test keyboard access and later scrolling separately.

  46. Frontend and design

    Load only the font weights you use.

    Each weight is another file on every page load. Fonts from a CDN also fail on locked-down networks, so self-host when needed.

    How it works

    The problem

    A page can download font files for weights it never uses, or rely on a font CDN that the intended network cannot reach.

    Why it happens

    Font declarations create resource dependencies. Static weight files add requests and bytes, while external hosting adds a network assumption to rendering.

    How to work through it

    1. Inventory the font families and weights actually used by the design.
    2. Remove unused font resources and keep sensible fallbacks.
    3. Self-host the required files when the deployment must work without the external font service.

    The solution

    If body copy uses regular and headings use medium, loading every available static weight adds no reading benefit. A variable font may cover the needed range in one resource, so inspect the actual files rather than counting CSS weight values alone.

    How to check it

    Check the browser’s network panel on a cold load and in the intended network environment. Expect only required font resources, readable fallback text and no dependency on an unreachable CDN.

    Where this stops

    Licensing, character coverage and subset choices still matter. Fewer files are useful only if the page keeps the glyphs and styles its readers need.

  47. Frontend and design

    Restraint can be a mode, not a new design.

    A reading page can be the same theme turned down: same palette and fonts, fewer colours, almost no motion.

    How it works

    The problem

    A visually active site can become tiring when the same treatment surrounds a long article. Building an unrelated reading theme can then fragment the design.

    Why it happens

    The palette and type can carry the identity without every accent and animation being active. Reading needs less competing emphasis.

    How to work through it

    1. Keep the core palette and font roles.
    2. Reduce the number of accent treatments on the reading page.
    3. Remove most motion and give the prose a calmer hierarchy.

    The solution

    Use the article view as a quieter mode of the same system. A heading can keep its familiar type while decorative blinking, repeated colour accents and competing widgets step back.

    How to check it

    Read a full passage at the intended viewport and check whether the next line and section are easy to follow. Compare the page with the main site to see whether the shared visual identity still holds.

    Where this stops

    Restraint does not fix poor contrast, line length or hierarchy. Some pages need richer interaction; use the mode according to the reading task rather than removing useful feedback everywhere.

  48. Frontend and design

    Refuse min > max. Don't show an empty page.

    An impossible filter returns no rows, which reads as 'nothing matched'. Reject it on both sides with a message.

    How it works

    The problem

    A range filter with its minimum above its maximum returns an empty page. The user can mistake an invalid request for a valid search with no matches.

    Why it happens

    The query is contradictory, but the interface treats it like an ordinary empty result. Client-only validation also leaves the API open to the same ambiguity.

    How to work through it

    1. Validate the range in the interface before submitting it.
    2. Apply the same rule on the server.
    3. Return a clear invalid-range response and preserve valid zero-valued bounds.

    The solution

    A minimum of 20 and maximum of 10 should ask the user to correct the range. A minimum of zero should remain a real bound, so presence checks must distinguish zero from a missing value.

    How to check it

    Test min below max, min equal to max, min above max and a zero bound. Expect only the contradictory range to be rejected, with server behavior matching the interface.

    Where this stops

    Validation must follow the field’s real units and inclusive or exclusive boundary rules. An empty valid result is still legitimate; it should simply be distinguishable from an invalid request.

  49. Frontend and design

    Accent fills and accent text need separate tokens.

    Bright lime works as a button fill but is unreadable as small text on white, so the text role gets a darker ink in light mode.

    How it works

    The problem

    A bright accent can work well as a button background while becoming unreadable when reused for small text on a light page.

    Why it happens

    Colour contrast depends on both the foreground and its background. A single accent value cannot safely serve every visual role.

    How to work through it

    1. Separate accent-fill and accent-text tokens.
    2. Choose a darker text accent for light backgrounds.
    3. Check both tokens in the actual components and themes where they appear.

    The solution

    Keep the bright lime for a filled control if its label has enough contrast. Use a darker green ink for a small link on white, so the visual identity stays related without sacrificing reading.

    How to check it

    Measure contrast for the rendered foreground/background pair, including hover and disabled states. Inspect small text directly; a large coloured swatch does not reveal the reading problem.

    Where this stops

    The right value depends on text size, weight and background. A dark-mode token may need a different value again, and colour should not be the only way a control communicates state.

  50. Frontend and design

    Taste is knowing which option is right before you can say why.

    It comes from having seen enough good and bad work. AI can copy preferences; it doesn't build taste.

    How it works

    The problem

    Several designs can satisfy the same requirements. An automated system can produce all of them without knowing which one fits the product best.

    Why it happens

    Taste comes from comparing enough good and bad work to recognise patterns before the full reason is easy to state. A list of preferences captures only part of that judgment.

    How to work through it

    1. Ask for a few concrete options with the same constraints.
    2. Compare the options against work you already consider strong.
    3. Choose a direction, then explain the specific differences that drove the choice.

    The solution

    Keep human judgment at the point where options are selected. For example, compare two page layouts for reading order and emphasis, then give the chosen layout and reasons back to the implementation process.

    How to check it

    Check whether a later revision preserves the qualities behind the choice. Merely matching the chosen colours or spacing is weaker evidence than preserving the intended reading experience.

    Where this stops

    This is a personal view of how human judgment and AI fit together. Taste still needs correction from users, requirements and evidence; confidence alone does not make a choice right.

Writing and knowledge

14 lessons

  1. Writing and knowledge

    Writing like me means keeping my structure, not copying my slips.

    My casual messages show how I explain a problem. They also contain grammar I do not want carried into a finished post.

    How it works

    The problem

    A writing sample mixes several things: the order of the argument, the words I choose, the detail I give and the speed at which I typed it. Copy all of that and a polished draft starts repeating casual errors. Smooth everything away and it loses the way I think.

    Why it happens

    Surface imitation treats every feature of a sample as part of the voice. My October 30 guidance separates the useful structure and directness from the casual register.

    How to work through it

    1. Choose an original sample that fits the kind of writing. A work explanation and a status update have different jobs.
    2. Follow its reasoning: state the point, explain the cause, show a worked example where useful and give the next step. Keep uncertainty tied to what is actually uncertain.
    3. Edit grammar and remove repeated points. Compare the draft with the sample again: does it still make the same kind of direct, concrete explanation?

    The solution

    Use my samples to guide the argument and level of detail, then apply a separate editing pass. A numbered update can stay numbered. A useful caveat can stay. A grammatical slip does not need to survive for the writing to sound like me.

    How to check it

    The vault records six original samples collected on October 30 and my preference to keep structure and directness while writing grammatically.

    Where this stops

    The samples cover several work messages, and most were supplied without original dates. They guide style; they do not establish when the work described in them happened.

  2. Writing and knowledge

    Delegated work can be mine without pretending I typed every command.

    My October 24 ruling allows I found or I built for work an agent did on my assignment, while the source names the run that did it.

    How it works

    The problem

    When an agent carries out my assignment, how should a post describe the result? Treating every first-person claim as a claim of manual execution makes the account awkward. Calling the work mine without keeping its execution record loses useful credit and evidence.

    Why it happens

    Ownership of the work and execution of each step are different facts. A post can describe the project from my perspective while the source record preserves who performed the work and what they actually established.

    How to work through it

    1. Check that the work was done on my assignment and that the record supports the finding or result being described.
    2. Keep the task or run in the source record, including the actual executor and the evidence it produced.
    3. Use first person for the supported project result. If the sentence depends on who executed a step, name that person or agent directly.

    The solution

    Separate authorship from the execution trail. I can own the work and explain what I found through it, while preserving the agent's contribution in the source. That keeps the account readable without rewriting how the work happened.

    How to check it

    The maintained writing rule records the October 24 ownership decision and explicitly says the source names the run. This entry explains that decision; it does not claim a new experiment was performed that day.

    Where this stops

    Delegation supports ownership of recorded work. It does not supply personal feelings, manual actions or future plans that the record never states.

  3. Writing and knowledge

    A file header is a barcode.

    A few key-value fields at the top let a person or an agent identify a file before reading the rest.

    How it works

    The problem

    A reader can open a source file and still spend time discovering what it is for. Repeating that search across many files makes both human navigation and agent context gathering slower.

    Why it happens

    A short header can expose stable metadata before the implementation. Like the barcode in the original lesson, it identifies the item quickly; it should not try to reproduce the whole item.

    How to work through it

    1. Choose a small project convention for fields that help navigation, such as purpose, main inputs and outputs, or the relevant configuration owner. Use comment syntax appropriate to the file type.
    2. Keep the header close to the code it describes and check mechanically for required keys where practical. Leave detailed explanation in the owning documentation rather than repeating it in every file.

    The solution

    Make the first screen answer where this file fits and what a caller needs to know. The source remains the authority for behaviour, so duplicated details need a clear upkeep cost.

    How to check it

    Give an unfamiliar reader only the header and ask where they would use the file and what they would read next. Then compare those answers with the actual implementation.

    Where this stops

    The source records a proposed convention; its exact field set was still open. Generated files, tiny modules and formats with strict headers may need a narrower treatment or an exception.

  4. Writing and knowledge

    Hand docs go stale. Generated docs go wrong.

    Nothing checks the prose of a hand doc, so it drifts. Generated docs are fluent, cited and sometimes false. Both need a drift test.

    How it works

    The problem

    Documentation can sound authoritative while describing a system that no longer exists.

    Why it happens

    Handwritten claims drift when code changes without a corresponding edit. Generated prose can cite a source while misreading its meaning or pointing at an old revision; fluency and citation presence do not validate the claim.

    How to work through it

    1. Separate reference facts from rationale and history.
    2. Tie important current claims to code, a stable owner or an executable check.
    3. Recheck citations and semantics against the revised system before calling the documentation current.

    The solution

    Illustrative: a generated paragraph naming the correct source file can still misunderstand which service owns a table. Check the claimed relationship, not merely whether the link opens.

    How to check it

    Change a representative configuration fact in a disposable revision. The drift check or review should identify the stale claim, and a source inspection should confirm the corrected statement.

    Where this stops

    Mechanical checks cover only claims they can evaluate. Intent, omitted topics and data semantics still need informed review, and an evaluated generator is not automatically an adopted documentation workflow.

  5. Writing and knowledge

    A version number in prose has no owner.

    It goes stale the day the pin moves and nothing fails. Point to the file that owns the version instead.

    How it works

    The problem

    A setup guide can recommend yesterday's version even while the release configuration is correct.

    Why it happens

    A copied version number has no enforced link to the pin that owns it. Moving the pin leaves the prose untouched, so both can look authoritative.

    How to work through it

    1. Identify the configuration field that owns the version.
    2. Replace routine prose copies with a pointer to that owner.
    3. Keep dated release history separately from instructions about the current version.

    The solution

    Illustrative: 'use the version in the managed settings file' survives a release bump, whereas a hardcoded number in three guides needs three independent edits and can silently diverge.

    How to check it

    Move the pin in a disposable change and inspect the instructions. Current guidance should still resolve to the new value, while historical release records should remain unchanged.

    Where this stops

    A pointer is useful only if the reader can access and understand the owner. An intentionally frozen example or historical release note may need an explicit number and date.

  6. Writing and knowledge

    A new owner should inherit a system, not a story.

    A handover full of 'then we tried' is history. The next maintainer needs the current design and how to run it.

    How it works

    The problem

    A handover can explain every past experiment while leaving the next maintainer unsure which configuration is current or how to start the service.

    Why it happens

    Chronology organizes decisions by when they happened. Operating a system requires a different order: current purpose, boundaries, setup, normal operation and failure handling.

    How to work through it

    1. Write the handover from the current working design, with prerequisites, commands and checks in execution order.
    2. Move the sequence of old changes into the changelog or decision records, linking only where a past decision explains a present constraint.

    The solution

    If three launch methods were tried and one remains supported, teach that method first. Keep rejected methods in their historical home rather than making the reader reconstruct the answer.

    How to check it

    Ask a new reader to identify the supported start command, configuration owner and first diagnostic step without reading the project’s chat history. Any missing answer belongs in the current document.

    Where this stops

    History remains useful for understanding trade-offs and reversions. The correction is to give it a clear place, not erase the evidence behind the current design.

  7. Writing and knowledge

    A fact stated twice will disagree.

    The doc bugs I traced came from one fact living in two places and ageing at different speeds. Give each fact one home and link to it.

    How it works

    The problem

    A reader can follow two valid-looking documents to different answers about the same setting or procedure.

    Why it happens

    Copied facts age at different speeds. Updating the implementation or one document does not notify every other paragraph where the same value was pasted.

    How to work through it

    1. Choose the authoritative home for the fact and make the other documents point to it.
    2. Where a value must appear in several outputs, derive those copies from the same source and check that the generation remains current.

    The solution

    For a default timeout, let the configuration own the value and keep a conceptual guide focused on what the timeout means. Link to the reference instead of maintaining another number in the guide.

    How to check it

    Change the value in a disposable example and trace every place a reader can encounter it. Expect linked or generated views to agree, with no independent copy requiring a remembered edit.

    Where this stops

    One home does not mean one enormous document. Keep ownership at the smallest useful boundary, and allow intentional snapshots when they clearly name their version.

  8. Writing and knowledge

    Write the doc for a junior or an outsider.

    The five Ws, the exact commands, the steps in order. The reader who needs a doc most has the least context.

    How it works

    The problem

    A runbook can make sense to its author and still leave a new engineer unable to carry out the first step.

    Why it happens

    The author silently supplies context: where a command runs, which identity it uses, what must exist already and what output means success. The reader who needs the document may have none of that.

    How to work through it

    1. Open with the purpose, scope, responsible role and prerequisites, then put commands in the order they are needed.
    2. For each important step, show where to run it, what it changes and the expected result, including what to do when the check fails.

    The solution

    For a deployment command, name the target host and required access before asking the reader to execute it. Put a verification command after the action rather than leaving “check it works” unexplained.

    How to check it

    Have a reader with no session history walk the procedure in a suitable test environment. Record where they need an unstated fact and revise that exact step.

    Where this stops

    A junior-friendly runbook still needs a defined audience. Do not bury the procedure under explanations of every tool, and do not imply a reader walkthrough has occurred until it has.

  9. Writing and knowledge

    A heading's closing hashes vanish.

    In CommonMark `# Drain procedure #` renders as 'Drain procedure', so a trailing # in a title is silently lost.

    How it works

    The problem

    A converted title can lose a literal hash at its end even though the generated Markdown looks close to the source.

    Why it happens

    In CommonMark, a space followed by trailing hashes can close an ATX heading. Those hashes are syntax, so the renderer omits them from the visible title.

    How to work through it

    1. When emitting source text into a Markdown heading, distinguish literal punctuation from the heading marker.
    2. Escape a literal trailing hash: for example, emit `# Drain procedure \#` when the visible title must end in a hash.

    The solution

    Test the rendered title rather than comparing raw Markdown alone. The unescaped `# Drain procedure #` and the escaped form carry different visible text.

    How to check it

    Render both tiny examples with the target parser. Expect the first to show “Drain procedure” and the escaped form to show “Drain procedure #”, with the same heading level.

    Where this stops

    Escaping rules depend on context. This fixes literal heading content; it does not justify escaping intentional Markdown syntax throughout the document.

  10. Writing and knowledge

    A '- - -' bullet becomes a horizontal rule.

    Content inside a list item can start a new block, so the text '- - -' turns into a thematic break and the bullet deletes itself.

    How it works

    The problem

    A converter can preserve every word and still lose what the reader sees. A bullet containing three separated hyphens is a useful example: the output text exists, but the rendered bullet disappears.

    Why it happens

    Markdown interprets text by position. The content column of a list item can begin another block, so punctuation that looks harmless inside a bullet can become a thematic break instead of literal text.

    How to work through it

    1. Create a tiny input whose bullet contains the literal characters you need to preserve. Keep that expected text separate from the converter.
    2. Render the emitted Markdown with the reader the product supports, then compare the visible result with the input. Token counts alone do not answer this question.
    3. Escape literal punctuation where a block can begin, and test each document format that can emit the same pattern.

    The solution

    Make escaping aware of position. For a literal three-hyphen bullet, emit `- \- \- \-`: the first hyphen marks the list item and the escaped hyphens are its visible text. Apply the rule in every emission lane.

    How to check it

    The regression should show a visible bullet with the intended hyphens. Removing the escape should make that assertion fail, even if a token-recall score stays perfect.

    Where this stops

    Intentional Markdown rules and literal source text need different treatment. Do not escape real formatting simply because its characters can also appear in ordinary text.

  11. Writing and knowledge

    A model's default prose sits in the middle of everyone's.

    To make it sound like you, give it your own writing samples and rules a reviewer can check, not adjectives like 'sound human'.

    How it works

    The problem

    Asking a model to “sound human” gives it little information about one particular person’s writing.

    Why it happens

    Without examples and concrete decisions, a model fills gaps with common phrasing. Broad adjectives do not tell it how the writer explains causes, marks uncertainty or structures a technical example.

    How to work through it

    1. Provide samples the person actually wrote and label their context.
    2. Extract a few observable rules, such as point first, small words and uncertainty tied to evidence.
    3. Review a draft against those samples and revise specific weak passages.

    The solution

    For example, “explain the cause with a worked domain example” gives a clearer target than “be authentic.” A reviewer can quote whether the example helped and whether each sentence advanced the explanation.

    How to check it

    Compare drafts blind using the same underlying facts. Ask which explains the point better and require quoted reasons, rather than accepting a self-reported style score.

    Where this stops

    Style matching must not invent experiences, feelings or outcomes. Samples guide expression; factual claims still need their own source, and inferred style rules remain proposals until adopted.

  12. Writing and knowledge

    Do, understand, what's true now.

    Docs split into runsheets, references and registers, plus frozen decision records for why. Mix them and every one gets worse.

    How it works

    The problem

    A document becomes harder to trust when instructions, current facts, task status and historical decisions are mixed on the same page.

    Why it happens

    These forms answer different questions and age differently. A current reference must change with the system, while a decision record should preserve why a choice was made at that time.

    How to work through it

    1. Put repeatable actions in a runsheet with prerequisites and checks.
    2. Keep current facts in a reference and changing inventories or status in a register.
    3. Record the reasoning behind a decision in a dated, preserved decision record.

    The solution

    For example, the command to restart a service belongs in its operating instructions. Why that supervisor was chosen belongs in the decision record, linked from the current reference if useful.

    How to check it

    Ask a fresh reader to find what to do, what is true now and why the design exists. Check each answer against its intended owner and remove duplicated facts.

    Where this stops

    The names are a useful local taxonomy, not the only documentation system. A small project can combine sections if the purpose and maintenance rules remain clear.

  13. Writing and knowledge

    Silicon docs are full of brackets that aren't links.

    Bit ranges like [31:0] look like Markdown link text. A converter that treats every bracket as a link mangles every register table.

    How it works

    The problem

    A converter can turn literal hardware bit ranges into links and quietly corrupt technical tables.

    Why it happens

    Square brackets serve more than one role. Markdown uses them in link syntax, while register documentation uses forms such as [31:0] as ordinary content. A pattern that treats every bracket pair as a link confuses the two.

    How to work through it

    1. Distinguish actual link syntax from literal or escaped bracket text.
    2. Keep bit ranges as text through conversion and later enrichment.
    3. Use fixtures with register tables, escaped brackets and real links in the same document.

    The solution

    For example, a register name followed by [7:0] should retain the range exactly, while [guide](target) should remain a link. Test both together so fixing one form does not disable the other.

    How to check it

    Inspect converted table cells and extracted link targets separately. A text-presence check alone can miss a range that survived in the wrong structure.

    Where this stops

    This is a syntax distinction, not a reason to disable all bracket handling. Richer Markdown constructs may require a proper parser rather than increasingly broad regular expressions.

  14. Writing and knowledge

    Many small graphs, joined by stable names.

    One giant graph is hard to navigate and many loose ones drift. Keep one per source and connect them like netlists at their ports.

Work and delivery

27 lessons

  1. Work and delivery

    Severity and blocking are different decisions.

    A finding can be serious without belonging to this change. Decide both how harmful it is and whether it makes the change’s promise false, then give it a clear next action.

    How it works

    The problem

    An adversarial review can always find another real weakness nearby. If every weakness blocks the change, review becomes an expanding hardening project with no clear finish.

    Why it happens

    Severity describes the harm if a defect is reached. Blocking describes the relationship between that defect and the change being reviewed. Combining them into one label makes an old severe issue and a new small regression look like the same delivery decision.

    How to work through it

    1. Write the change’s promise before review: the behaviour, affected paths and checks it commits to. Keep that promise available beside the findings.
    2. For each finding, ask whether the change broke working behaviour or whether an explicit promise is false. A blocking finding should name that regression or the promise it contradicts.
    3. Record other real defects where the owning project keeps leftover findings. Keep severity for prioritisation, and include them in the same repair when they share its cause and fit its boundary.

    The solution

    Use two decisions: how bad is it, and what happens next? Review can then close its bounded job while leaving an honest, owned record of work still to do.

    How to check it

    Try the rule on two illustrative findings: an old high-impact bug outside the promise, and a new low-impact regression inside it. Their severities differ from their blocking decisions; neither should disappear from the record.

    Where this stops

    The source records an adopted ranking method and reported project use, not a measured reduction in review time. An old defect that makes the current promise false still blocks under this method; urgent release-wide risk may also need an owner decision.

  2. Work and delivery

    Pin who owns the check, not just its passing threshold.

    A passing number is weak evidence when the person producing the result can replace the script that measures it. The measuring rule needs an owner as well as a threshold.

    How it works

    The problem

    A workflow may faithfully record a script, its inputs and a passing measurement. That still leaves a gap if the same worker can swap the real check for a script that prints the expected number.

    Why it happens

    The workflow proves which script produced the number, but it has not established that this script was authorised to judge the work. Recording evidence and choosing the acceptance instrument are separate responsibilities.

    How to work through it

    1. Name the check owner when agreeing the acceptance criteria. Let that owner choose the script and the inputs it must inspect.
    2. Bind the accepted script version to the work record, for example by its content hash, and reject results from a different version.
    3. Review whether the script actually measures the promised behaviour. Keep quality review and any required human sign-off explicit alongside the deterministic result.

    The solution

    Move control of the acceptance instrument out of the unchecked worker path. A pinned script gives review a stable object; review still has to decide whether that object is a useful check.

    How to check it

    A useful proposed regression replaces the script with one that prints a passing value. Submission should refuse that result before considering its number. Then run the authorised check against both correct and deliberately broken work.

    Where this stops

    The source identifies this ownership gap and proposes hash-pinning the check; it does not establish that this correction was implemented. Pinning alone cannot make a vacuous or incomplete check meaningful.

  3. Work and delivery

    Reversible questions shouldn't wait for me.

    My work stalled while I slept. Now a reversible call is made and logged as what, why and cost if wrong; only irreversible ones wait.

    How it works

    The problem

    Work can stop for hours behind a small question whose answer is easy to change. When the owner is unavailable, an agent may preserve uncertainty by doing nothing even though the next step is reversible.

    Why it happens

    Waiting treats every choice as if it had the same cost of being wrong. A local implementation detail and an irreversible external commitment need different decision boundaries.

    How to work through it

    1. Identify what would change, whether it can be undone and the likely cost of a wrong choice. Check the task's existing authority before deciding that work can proceed.
    2. For a reversible choice within that scope, make a written ruling stating what was chosen, why and how to reverse it. Continue the dependent work and keep the ruling easy for the owner to inspect.

    The solution

    Use the record to support momentum without hiding judgement. It gives the owner a concrete result to review and makes a mistaken assumption cheaper to find and correct.

    How to check it

    Review the next handoff for silent waits and undocumented choices. A reader should be able to identify the decision, its effect and the recovery path without reconstructing the chat.

    Where this stops

    Irreversible, external or tightly coupled actions still need the relevant authority. Reversibility is not permission to expand scope, and a cheap local edit can still create expensive downstream obligations.

  4. Work and delivery

    Blame the process, not the person.

    People only record mistakes if the record can't be used against them. A captured mistake should make the team ask why, not who.

    How it works

    The problem

    People are less likely to record a mistake when the record can later be used against them. A lessons system can therefore lose the evidence it needs precisely where the work was most difficult.

    Why it happens

    Useful capture depends on trust. If the expected consequence is blame, a person has a reason to write a safe summary instead of the real conditions that allowed the error.

    How to work through it

    1. Agree how mistake records may be used before asking for detailed capture. Focus the record on the workflow, missing information, failed control and the change that could prevent a repeat.
    2. Separate improvement work from individual performance decisions, with clear access and retention rules. Ask why the process allowed the outcome before asking which person to hold responsible.

    The solution

    Make the response to a captured mistake produce a safer process. Follow through on the agreed limits so the system's behaviour supports its promise of learning.

    How to check it

    Review a sample of records for actionable causes and corrections. Ask contributors whether they can report uncertainty and slips without the record becoming a search tool for personal blame.

    Where this stops

    Anonymity is weak in a small team because context can identify someone. Serious conduct concerns and repeated patterns need a clearly governed route; they should not be smuggled into a learning archive.

  5. Work and delivery

    Capture the mistake when it happens.

    A lessons-learnt review at the end is short and tidied by hindsight. Record the fix or the slip while the detail is still there.

    How it works

    The problem

    An end-of-project review can preserve the conclusion while losing the details that made a fix useful. By then, the failed assumption and the evidence that changed someone's mind are harder to reconstruct.

    Why it happens

    Hindsight compresses a messy sequence into a tidy story. Capturing near the event preserves the symptom, context and attempted correction before memory turns them into an apparent straight line.

    How to work through it

    1. Record the problem and the smallest evidence that explains it while the work is fresh. Keep the source, relevant versions, change made and what was actually checked.
    2. Separate a tested result from a hypothesis or a proposed fix. Later, turn the capture into a reusable explanation and link it to any rule or check it changes.

    The solution

    Make capture part of the existing workflow so it does not depend on a large retrospective meeting. A short accurate record is a better starting point than a polished account written from memory.

    How to check it

    Ask someone outside the original task to explain when the lesson applies and how to check the correction. Missing steps or unclear limits show what the capture still needs.

    Where this stops

    Immediate notes can be incomplete and sensitive. Review them before reuse, respect contractual limits on design details, and do not turn a mistake record into an individual's performance file.

  6. Work and delivery

    Rulings in a chat are not a backlog.

    Decisions made in conversation get lost or overruled by older drafts. Write each one where the work queue lives.

    How it works

    The problem

    A decision made in chat can disappear from the build plan. An older queued draft may then be implemented faithfully even though it contradicts the direction the owner most recently chose.

    Why it happens

    Conversation records intent, but the work queue determines what someone picks up next. If the two are not connected, memory and handoffs become competing sources of authority.

    How to work through it

    1. Write an actionable ruling into the project's durable work record in the same session, including its date, reason and the item or requirement it changes.
    2. Before starting a queued item, compare it with rulings made since the draft was written. Rewrite or drop a conflicting item and record the reconciliation before building from it.

    The solution

    Give every actionable decision a route to work, and give each piece of work a current decision basis. A chat link can supply context, but it should point to a maintained row rather than substitute for one.

    How to check it

    Sample recent decisions and trace each to its queue item, then trace queued items back to their latest checked ruling. An unqueued commitment or obsolete draft should become a visible finding.

    Where this stops

    Preferences already maintained elsewhere do not all need new tasks. The record must also distinguish an owner's decision from an agent's proposal so a summary cannot silently grant authority.

  7. Work and delivery

    Decide slowly, then move fast.

    Before you build is the cheap window. Lay everything out while being wrong still costs an edit; once a project ramps up, every mistake costs more and the team ends up firefighting.

    How it works

    The problem

    Building quickly can make a wrong assumption expensive before anyone sees it. Once code, dependencies and schedules form around the choice, a small correction can turn into substantial rework.

    Why it happens

    Early sketches are cheap to change because they have few consumers. Laying out goals, workflows and interfaces exposes mismatches while removing or moving a block still costs an edit.

    How to work through it

    1. Sketch the whole system far enough to show its users, main flows, dependencies and acceptance checks. Use an earlier project as a starting point, cutting what does not fit rather than inheriting every feature.
    2. Challenge the risky assumptions with small prototypes and feedback. Then select a narrow working slice, keeping the wider layout open to revision as evidence arrives.

    The solution

    Spend early effort on decisions that would be costly to reverse. The sketch should make choices easier, not become a promise to build every box it contains.

    How to check it

    Ask whether a reviewer can trace the first slice from the goal through its interfaces to a concrete check. Unanswered questions that block that path need a decision or a bounded experiment.

    Where this stops

    Software can be cheap to change, so endless design has its own cost. Estimates from one earlier project remain uncertain; a reusable blueprint is evidence to adapt, not a fixed multiplier.

  8. Work and delivery

    Silence is the absence of reporting, not of problems.

    A block with no regression results isn't passing. No evidence means unknown, never green.

    How it works

    The problem

    A dashboard can look healthy because nothing reported a failure. That is misleading when a block never ran its regression or the reporting path stopped collecting results.

    Why it happens

    A missing report says nothing about the underlying work. Treating absence as success collapses two different states: checked and passed, and not currently known.

    How to work through it

    1. Define the evidence required for each passing state, including the relevant inputs and completion result. Represent missing or stale evidence explicitly instead of filling it with a green default.
    2. Track reporting coverage beside pass and fail counts. If an expected producer goes silent, surface that gap so someone can distinguish no work, failed collection and an unfinished run.

    The solution

    Let a green status follow evidence that actually meets the acceptance rule. A recorded statement about outside work can remain useful while waiting for a check inside the trusted workflow.

    How to check it

    Remove an expected result from an illustrative fixture. The summary should become unknown or incomplete rather than improve its apparent pass rate.

    Where this stops

    Not every inactive component is faulty. A deliberate exclusion or waiver needs its own visible meaning, and a zero-item run should not satisfy a requirement that expected real checks.

  9. Work and delivery

    Record proof that goes stale, not status people type.

    A ticket says 'done' until someone edits it. Evidence tied to a hash goes stale by itself when an input changes.

    How it works

    The problem

    A ticket marked done can stay green after the requirement or artifact it refers to has changed. The status records someone's earlier conclusion but has no way to notice its inputs moved.

    Why it happens

    A content hash identifies particular bytes. Evidence bound to those bytes can be compared with the current inputs, so a changed dependency makes the old result stale without anyone editing the ticket.

    How to work through it

    1. Record the artifact, its digest, the inputs it depends on and the check that produced the verdict. Keep a path or address that lets a reviewer recover the evidence.
    2. When an input changes, compare its digest with the recorded one and invalidate the dependent result. Preserve the earlier evidence as history rather than silently overwriting its meaning.

    The solution

    Make the status a view of current evidence. A previous pass is still useful, but it should not stand in for a pass on a different requirement or build.

    How to check it

    Change one consumed input in an illustrative trial. Its dependent result should become stale, while unrelated results remain current and the earlier artifact remains inspectable.

    Where this stops

    Hashes prove identity and help detect change; they do not prove the original check was sound. Missing dependencies and an untrusted producer can still make a precise record misleading.

  10. Work and delivery

    Buy the commodity. Build the differentiator.

    A missing 10% is rarely a reason to rebuild the other 90%. Wrap it, extend it, or write a plugin.

    How it works

    The problem

    A useful product may lack one feature that matters to a particular workflow. Replacing the whole product to obtain it also makes the team responsible for every capability that already worked.

    Why it happens

    The missing feature is visible; the cost of rebuilding ordinary functions is easier to overlook. Authentication, upgrades and compatibility still need an owner after the replacement is written.

    How to work through it

    1. List the precise gap and the constraints around it. Check whether a wrapper, preprocessing step, extension point or plugin can supply the missing behaviour.
    2. Build a small end-to-end trial around that boundary. Compare its maintenance and integration cost with the cost of owning a replacement, including the routine features users expect.

    The solution

    Prefer an extension when it can satisfy the real requirement without fragile coupling. Keep custom work at the boundary where the existing product stops being sufficient.

    How to check it

    Run a representative workflow through the extended product and confirm that ordinary features still work. The gap should be closed without forcing users to recover lost basics elsewhere.

    Where this stops

    Some constraints justify replacement: incompatible security boundaries, blocked deployment or a core model that cannot express the task. The percentages in the short lesson are a decision prompt, not a measured cost ratio.

  11. Work and delivery

    Ports freeze at tape-out. Software interfaces don't.

    So version them, and let both sides cope with the old shape for a while.

    How it works

    The problem

    A software interface can change while an older client is still in use. Renaming or removing a field in one deployment can therefore break a caller that was correct when it was released.

    Why it happens

    Unlike a port frozen at chip tape-out, a software contract evolves across independently deployed parts. Without a version or compatibility period, both sides must change at exactly the same time.

    How to work through it

    1. Describe the old and new shapes and identify which deployed consumers still depend on the old one. Decide whether the change can be additive or needs an explicit new contract version.
    2. Let the producer accept or serve the older shape during a defined transition, and update consumers in an order that preserves a working path. Record when support for the old shape can end.

    The solution

    Make interface change a migration with a compatibility window. This keeps a small internal refactor from becoming an unplanned coordinated release.

    How to check it

    Exercise old and new clients against the transition version. Each supported combination should behave as documented, and an unsupported version should fail with a clear explanation.

    Where this stops

    Compatibility costs code and tests, so retire old paths deliberately. In a single process with an atomic release, a long public versioning scheme may add little value.

  12. Work and delivery

    Cap the review loop.

    Each round finds small issues in the last round's fixes. Without a severity cap, a two-line fix grows into thousands of lines.

    How it works

    The problem

    A small fix can turn into an unbounded campaign when each review round expands the set of issues worth fixing.

    Why it happens

    Review creates useful new observations, but it also finds minor issues introduced by earlier corrections. Without a severity and scope rule, the stopping point moves after every pass.

    How to work through it

    1. Define the blocking severity and intended scope before review.
    2. Fix findings that cross that boundary and record the rest separately.
    3. Run the agreed final gate on the exact revised change and stop when its blockers are resolved.

    The solution

    Illustrative: a cosmetic naming suggestion discovered during a narrow correctness fix can go into a follow-up rather than trigger another broad refactor and review cycle.

    How to check it

    Check that the final verdict names the reviewed revision, unresolved blockers and deferred findings. The stop should follow an explicit rule rather than fatigue or an arbitrary round count.

    Where this stops

    A severity cap is not permission to ignore a serious newly discovered issue. If evidence changes the risk or scope materially, revise the work boundary explicitly.

  13. Work and delivery

    When prototypes take hours, alignment is the bottleneck.

    Building stopped being the slow part. Agreeing on direction is, so talk it through together and early.

    How it works

    The problem

    A team can build competing solutions quickly when it has not agreed which problem matters.

    Why it happens

    Faster prototyping reduces the cost of implementation but increases the cost of discovering disagreement late. Unstated direction lets each contributor optimize a different result.

    How to work through it

    1. Agree on the user need and a concrete success condition early.
    2. Make unresolved choices visible before parallel implementation.
    3. Use a small reviewable artifact to settle the next decision together.

    The solution

    Illustrative: two people may both build a good dashboard while one optimizes investigation and the other optimizes a weekly summary. A shared reader task exposes that difference before both layouts are finished.

    How to check it

    Ask each contributor to describe the goal and acceptance condition independently. Material disagreement means the direction still needs discussion before more implementation is useful.

    Where this stops

    Alignment is not unanimity about every detail. Keep routine reversible choices with their owner, and return to shared discussion when new evidence changes the goal or its important constraints.

  14. Work and delivery

    The future engineer orchestrates.

    AI is the hands: fast, tireless, good at patterns. The human is the slower brain that holds the taste and makes the call.

    How it works

    The problem

    Fast generation can leave a project with many changes but no clear decision about which result is right.

    Why it happens

    Execution speed does not supply the human preferences, trade-offs and purpose that define success. Agents need bounded tasks and observable checks, while the owner still decides direction and judges the result.

    How to work through it

    1. State the goal and the choices that require human judgment.
    2. Give the agent a clear contract and the evidence it needs.
    3. Review concrete output against that contract before expanding the work.

    The solution

    Illustrative: an agent can produce several layout variants quickly. Choosing which best serves the intended reader still needs a stated task and a person's judgment about the trade-off.

    How to check it

    Ask whether the handoff lets the agent act independently and whether the reviewer can explain why the chosen output fits the goal. Speed alone should not be the acceptance condition.

    Where this stops

    This describes a working philosophy, not a measured claim that a fixed human-agent split suits every task. Some choices need specialist review or deterministic checks as well as taste.

  15. Work and delivery

    Small teams with guardrails win.

    AI multiplies a small team once linters, rule decks and checklists are in place. Without them it multiplies chaos.

    How it works

    The problem

    A small team can generate more changes with AI while spending more time untangling inconsistent interfaces, unreviewed assumptions and partially finished work.

    Why it happens

    Fast output magnifies the workflow around it. Without shared contracts and observable completion, each additional change creates more uncertainty for the next person or agent.

    How to work through it

    1. Give the team a common set of interfaces, rule decks and required checks before increasing the number of parallel tasks.
    2. Assign bounded work with explicit completion evidence, and reserve human review for requirements, trade-offs and choices the checks cannot decide.

    The solution

    For a small feature, define its input, output and end-to-end acceptance path. Let tools check syntax and contracts while a reviewer asks whether the result actually serves the user.

    How to check it

    Try one representative change through the full path. Inspect whether a broken contract is rejected and whether a reviewer can understand the outcome without reconstructing the whole session.

    Where this stops

    This is a working preference, not a measured claim that every small team beats every large one. Team size, domain knowledge and coordination costs still affect what the workflow can support.

  16. Work and delivery

    A waiver is a decision. Record it like one.

    Tool-specific waiver files scattered around hide who accepted which risk and why. A real waiver has an owner, a reason and a date.

    How it works

    The problem

    A tool can suppress a warning long after the reason for accepting it has been forgotten. The next maintainer sees silence without knowing which risk remains.

    Why it happens

    A local suppression file records a machine instruction, but may omit the person accountable for accepting the exception, its scope and its expiry.

    How to work through it

    1. Record the failing check, the exact case covered, the accountable owner and the reason for acceptance.
    2. Define the ending condition required by the waiver contract, then link the tool-specific suppression to that decision. For a milestone-based contract, expiry is a downstream milestone passing, not a calendar date.

    The solution

    For example, waive one named violation in one bounded configuration. Keep the same check active elsewhere, so the exception cannot quietly become a project-wide opt-out.

    How to check it

    Ask a reader what is still failing, who accepted it and what ends the exception. Check that an out-of-scope violation still fails and the waiver expires when its specified milestone passes.

    Where this stops

    A record makes risk visible, not automatically acceptable. The original card’s date does not define expiry: a milestone-based contract binds acceptance to scoped evidence and a structural milestone, and rejects calendar expiry.

  17. Work and delivery

    A tool nobody updates is worse than none.

    People trust stale data. Put the truth next to the work so updating it is a side effect, not a chore people skip.

    How it works

    The problem

    A tracker can keep showing a confident status long after the work has moved on. The danger is not an empty field; it is a field people still trust.

    Why it happens

    When recording progress is a separate chore in another interface, it competes with the work itself. A complete tool can still become stale if using it is awkward or its updates have no natural owner.

    How to work through it

    1. Find the action that already establishes the fact: a completed run, an approved change or a recorded requirement decision.
    2. Capture that evidence beside the work and let other views read from it. Keep manually entered claims distinct from results that have an execution record.
    3. Give readers a way to see freshness and uncertainty. Test the workflow with the people who must update it, not only with the person building the tool.

    The solution

    Make keeping the record current a side effect of doing the job. A familiar dashboard can remain a view without becoming a second place where the same truth must be retyped.

    How to check it

    Follow one completed task from its actual evidence to the displayed status. Then change the work and check whether the view updates or visibly becomes stale.

    Where this stops

    Automatic collection does not make an interpretation correct. Decisions, exceptions and human judgment still need clear ownership and a way to be corrected.

  18. Work and delivery

    Agents should cite what they worked from.

    Work that can't be traced to a spec or a requirement can't be trusted, however good it looks.

    How it works

    The problem

    An agent can produce a convincing implementation while solving the wrong version of the problem. A reviewer needs to know which requirement or decision the work was actually based on.

    Why it happens

    A polished explanation hides its assumptions unless the source is named. Without that connection, the reviewer must reconstruct the brief before deciding whether the result meets it.

    How to work through it

    1. Give the agent the relevant requirement, specification or decision before it starts. Include the exact revision when the source can change.
    2. Ask the handover to connect the important outputs to the source passages that require them. A list of document names is less useful than a clear claim-to-source connection.
    3. Open each cited passage during review and check that it supports the actual behaviour being claimed, including any limit or exception.

    The solution

    Keep the source trail with the work. The citation should help the next engineer answer both why this exists and what would need rechecking if the requirement changed.

    How to check it

    Take one implemented behaviour and follow its citation back to the brief. If the passage does not require or explain it, the work still needs a reason.

    Where this stops

    A citation is a route to evidence, not proof on its own. A wrong or stale citation can make unsupported work look more credible.

  19. Work and delivery

    Ship it end to end, or don't ship it.

    A stub 'for later' is a ghost: it looks like a feature and does nothing. Half-built is worse than missing.

    How it works

    The problem

    A feature can look finished because its button, route and data shape exist. The user only discovers the missing part when they try to complete the job the feature promised to do.

    Why it happens

    A stub satisfies the structure of an implementation without satisfying its purpose. Once it looks like a real feature, other work starts depending on behaviour that has never existed.

    How to work through it

    1. Describe one complete user outcome, including the input, the visible result and the way a failure is reported.
    2. Follow that path through the interface, processing and storage. Name each handoff rather than treating a working screen as the end.
    3. Test the path with a real input and a representative failure. Keep unfinished work out of the shipped promise until that path works.

    The solution

    Ship a smaller complete slice. Reducing scope preserves a usable outcome; leaving a hidden stub inside the promised outcome does not.

    How to check it

    A new reader should be able to start from the documented entry point and obtain the stated result without the author filling in a missing step.

    Where this stops

    This is a shipping standard, not a ban on prototypes. A prototype can contain stubs when its limits are visible and nobody mistakes it for the finished feature.

  20. Work and delivery

    The judge should not be the author.

    For a merge, use reviewers who didn't write the code and ask for zero blockers. Self-review only finds what you already looked for.

    How it works

    The problem

    An author reviewing their own change can confirm the intended design while missing the assumptions that made the bug possible.

    Why it happens

    The author already knows the expected path and may unconsciously supply missing context. A fresh reviewer is better placed to challenge that path and inspect whether the tests defend the actual contract.

    How to work through it

    1. Give a reviewer who did not write the change its requirements and exact diff.
    2. Ask for concrete blocking findings with evidence, rather than a general approval score.
    3. Resolve those findings and run the required checks on the integrated result.

    The solution

    For example, a reviewer can ask what happens when a dependency fails even if the author tested the successful response thoroughly. The finding should name the broken behavior and a way to reproduce it.

    How to check it

    Confirm that the reviewed revision matches the candidate being merged and that each blocker has an explicit resolution. Separate individual branch checks do not prove the merged tree.

    Where this stops

    Fresh review is another source of evidence, not proof of perfection. Match its depth to risk, and keep reviewer uncertainty visible instead of forcing every comment into approve or reject.

  21. Work and delivery

    Don't buy a gateway that skips the hard part.

    A gateway that moves requests but not identity leaves the real problem where it was. A thin wrapper you own can do the one thing that matters.

    How it works

    The problem

    A request gateway can add another hop without solving the access-control problem that justified introducing it.

    Why it happens

    Forwarding a shared service key authenticates the service to the destination. It does not tell the destination which person asked, or restrict that person to their permitted records.

    How to work through it

    1. Trace caller identity from the entry point to each protected operation.
    2. Check whether the destination enforces per-user scope or expects the gateway to do so.
    3. Choose the smallest maintained layer that can authenticate callers, authorize arguments and protect the shared credential.

    The solution

    In an illustrative ticket assistant, the wrapper must bind a request to the authenticated caller and verify record ownership before using a broad key. Routing alone adds no such check.

    How to check it

    Try an allowed request and a cross-user request through the real entry point. The latter should fail before protected data is returned, including when a caller changes supplied identifiers.

    Where this stops

    A thin wrapper still needs review, maintenance and secure key handling. A managed gateway may be appropriate if it provides the actual identity and policy features required.

  22. Work and delivery

    Imagine it already failed.

    Asking 'what could go wrong' gets polite answers. Declaring that the project failed makes finding reasons the task, and makes doubt safe to say.

    How it works

    The problem

    A plan review can produce agreement without exposing the reasons the project might fail.

    Why it happens

    People asked to find risks may defend a plan they helped create. A pre-mortem changes the task: assume failure has happened, then explain it, so raising a concern becomes part of the assignment.

    How to work through it

    1. Describe the plan, then state an imagined failure at a defined future milestone.
    2. Have each person write possible reasons alone before sharing.
    3. Turn each useful reason into a plan change, a check or an explicitly accepted risk with an owner.

    The solution

    Use a concrete imagined failure rather than an open invitation for concerns. For example, “the release missed its reliability target” directs attention toward dependencies and missing evidence that a general discussion may skip.

    How to check it

    Check whether the exercise changed the plan or created owned actions. A long list with no decisions is an unfinished review.

    Where this stops

    This is a proposed review method, not evidence that a particular project used it. It cannot find risks nobody can imagine, and the source’s research claims were not independently checked.

  23. Work and delivery

    For a service company, the process is the product.

    The customer keeps the chip; the company keeps only how it was delivered. So capture lessons on the spot, not in a hindsight review.

    How it works

    The problem

    A delivery organization can finish a project while losing the knowledge of how its team solved the difficult parts. The customer retains the deliverable; the process lessons may remain only in memory.

    Why it happens

    An end-of-project summary is both late and compressed. It can omit the decisions, failed assumptions and local checks that made the lesson transferable.

    How to work through it

    1. Capture a mistake or useful practice when it happens.
    2. Record the problem, cause, correction and evidence in enough detail to reuse.
    3. Turn a recurring lesson into a maintained process or check with an owner.

    The solution

    If an input handoff fails, capture which contract was missing and how to detect that condition next time. That gives the next team a correction they can apply, rather than only a story that the project was difficult.

    How to check it

    Ask a fresh reader whether the record tells them when the problem applies and how to check the correction. A lesson has transferred when it supports a usable change.

    Where this stops

    This is the source’s organizational argument, not a measured company outcome. Capture should improve the process and avoid assigning blame to an employee.

  24. Work and delivery

    Keep the claim and today's state in separate columns.

    Mix what it will do with what it does now and you'll be issuing corrections. Two columns keep the claim honest.

    How it works

    The problem

    A product description can imply that a planned capability already works. Later status reports then need corrections even when the implementation team made no new mistake.

    Why it happens

    A statement about the intended product and a statement about the current build answer different questions. Combining them hides the evidence boundary.

    How to work through it

    1. Write the product claim in one column.
    2. Write the currently supported state and its evidence in another.
    3. Name the missing work needed to close the gap.

    The solution

    For example, “tracks delivery evidence” can be the product goal while today’s state says which records exist, which checks run and which integrations remain planned. A reader can then assess both the direction and the usable capability.

    How to check it

    For each current-state sentence, point to a present artifact or recorded check. If the only support is a plan, move it out of the built-state column.

    Where this stops

    A two-column layout is a writing aid, not an implementation guarantee. Keep the state dated or versioned and revisit it when the product changes.

  25. Work and delivery

    Keep a PR under 500 hand-written lines.

    A reviewer can read 500 lines line by line. Past that, review turns into skimming. Generated files don't count toward it.

    How it works

    The problem

    A large pull request can pass review while receiving less line-by-line attention than its author expects.

    Why it happens

    As hand-written changes grow, the reviewer has more intent and interaction to hold at once. The source’s size bar is meant to keep the review unit readable.

    How to work through it

    1. Cut work into coherent slices with an explicit intended behavior.
    2. Keep each review around the stated hand-written line budget.
    3. Separate generated output from the hand-written change and explain how it is reproduced.

    The solution

    A small API behavior change should carry its implementation, tests and needed documentation together. Splitting by behavior gives the reviewer one claim to follow from input to result, rather than several unrelated partial changes.

    How to check it

    Inspect the actual hand-written diff and ask whether each line can be tied to the PR’s stated intent. If several independent intents appear, split the review before asking someone to skim the whole batch.

    Where this stops

    The 500-line bar is a review policy, not a measured human limit. A smaller diff can still be complex, and generated changes still need appropriate validation.

  26. Work and delivery

    Slice vertically, not horizontally.

    'All the error handling everywhere' is too broad to test and easy to miss. 'Error handling and logging for one part' has a clear edge and a clear test.

    How it works

    The problem

    A task such as “fix error handling everywhere” spreads across too many paths to test as one coherent change.

    Why it happens

    The work is cut by concern across the whole system rather than by a bounded behavior. Ownership and the test boundary become unclear.

    How to work through it

    1. Define shared error and logging contracts first.
    2. Choose one part of the system with a clear input and output.
    3. Plan, test, implement and review that part end to end before widening the work.

    The solution

    A slice might handle validation errors and logging for one import path. It can be exercised from bad input to the final response, so missing behavior has a clear owner.

    How to check it

    Check that each promised behavior has a test within the slice and that the slice agrees with the shared contract. Then inspect the seam where it hands work to the next part.

    Where this stops

    Vertical slices do not remove cross-cutting work. Without a common contract and an integration pass, several small slices can still produce inconsistent error behavior.

  27. Work and delivery

    Tie deadlines to milestones, not dates.

    Date deadlines go overdue on paper whenever a schedule slips, which trains reviewers to clear the flag instead of the debt.

    How it works

    The problem

    A deferred task becomes overdue whenever the schedule slips, even if the project has not reached the stage where that task matters. Repeated false alarms make the warning easy to clear.

    Why it happens

    A calendar deadline and a delivery dependency answer different questions. Work tied to a milestone should become overdue when that milestone passes.

    How to work through it

    1. Give the deferral an owner and a reason.
    2. Name the milestone by a stable identifier.
    3. Compute overdue status from that milestone’s current state, keeping the debt visible until it is resolved.

    The solution

    For example, attach a required review to “integration passed.” If integration moves, the review still has the same trigger; no date needs to be edited just to make the dashboard quiet.

    How to check it

    Change the milestone from pending to passed and expect the unresolved deferral to become overdue. Moving an unrelated calendar date should not discharge it.

    Where this stops

    Use calendar deadlines where time itself is the obligation, such as a contract or certificate expiry. Milestone-based expiry is useful for delivery dependencies, not every kind of deadline.

Personal learning

6 lessons

  1. Personal learning

    Jump in, then work backwards.

    With something new I start from a working example and take it apart until I understand each piece.

    How it works

    The problem

    A new tool can be easy to run before its behavior is easy to explain.

    Why it happens

    Starting from a working example provides concrete inputs and outputs, but copying it unchanged can hide prerequisites and failure paths. The learning comes from taking apart why each part is present.

    How to work through it

    1. Choose a small working example that matches the task.
    2. Trace one input through its transformations to the output.
    3. Change one element at a time and explain the resulting behavior before expanding the example.

    The solution

    Illustrative: remove one configuration value and predict what should fail. The difference between the prediction and the result identifies the next piece of the mental model to learn.

    How to check it

    Explain the example without reciting its code, then rebuild the smallest useful version from that explanation. Note the setup assumptions that were initially invisible.

    Where this stops

    This is a learning preference from the source, not a proven universal method. It can skip foundations, so check prerequisites explicitly before applying the example to a larger system.

  2. Personal learning

    Be T-shaped.

    Broad enough to design the whole system, deep enough in one area that people come to you. In a fast-moving field, breadth is underrated.

    How it works

    The problem

    Knowing one tool deeply can leave gaps at its boundaries; knowing many tools lightly can leave nobody sure where your judgment is strongest.

    Why it happens

    System design needs enough breadth to understand how parts fit, while difficult local decisions need depth built through repeated work and feedback. These are different needs, and neither replaces the other.

    How to work through it

    1. Choose one area where you want to own difficult decisions.
    2. Build enough knowledge of adjacent areas to understand interfaces and trade-offs.
    3. Use real problems to expose the next gap rather than collecting tools without applying them.

    The solution

    For example, a front-end hardware designer benefits from understanding verification and physical constraints, while keeping a deeper specialty in architecture or RTL. That range helps ask the right questions without claiming equal mastery of every stage.

    How to check it

    Check whether you can explain an end-to-end design and defend a hard decision in your specialty. Ask an expert in an adjacent area where your assumptions break.

    Where this stops

    The right balance depends on the role and project. T-shaped is a useful direction, not a score or a promise that breadth automatically creates expertise.

  3. Personal learning

    If I make the same point a third time, stop me.

    Holding a position is a strength until it outlasts the evidence. Repeating without new support is the tell.

    How it works

    The problem

    A technical discussion can stall when the same argument is repeated after the evidence has shifted. Repetition can sound like stronger support without adding any.

    Why it happens

    The source describes a tendency to hold a position too long. The useful trigger is a repeated claim with no new evidence, rather than disagreement itself.

    How to work through it

    1. State the decision and the evidence currently supporting it.
    2. When the same point returns, ask what new fact it adds.
    3. Compare the strongest contrary evidence and name what would change the decision.

    The solution

    For example, if a preferred design is defended a third time with the same performance claim, return to the measurement behind that claim. If no measurement exists, mark the claim as an assumption and decide how to check it.

    How to check it

    Look for a change in the evidence or a clear next experiment. Agreement caused only by pressure does not settle the technical question.

    Where this stops

    This is a personal collaboration preference, not a rule to cut off discussion after a fixed count. A repeated warning may still matter if it was never answered.

  4. Personal learning

    Games teach you to think one move ahead.

    Plan the move, predict the response, adapt. Thousands of hours of that loop turned out to be debugging practice.

    How it works

    The problem

    Debugging becomes slow when each change is a reaction to the latest symptom. There is no prediction to compare with what happens next.

    Why it happens

    The gaming loop named in the source is to plan a move, predict the response and adapt. Applied to engineering, it turns an edit into a test of a specific model of the system.

    How to work through it

    1. State the current hypothesis and what it predicts.
    2. Choose one move that should produce a different result if the hypothesis is wrong.
    3. Compare the response with the prediction before choosing the next move.

    The solution

    For example, if a failure is blamed on cached state, predict what a clean state should change. Then reset only that state and inspect the result, rather than changing the cache and several settings together.

    How to check it

    Write the expected result before running the experiment. A useful result distinguishes competing explanations, even when it disproves the preferred one.

    Where this stops

    This is a practical reasoning pattern inspired by the source’s account. It does not establish that time spent gaming causes debugging skill, or replace knowledge of the system being tested.

  5. Personal learning

    Music taught me structure.

    Grade 8 piano: themes, variations, tension and resolution. The same shapes show up in how I lay out a system.

    How it works

    The problem

    A system can contain sensible parts and still feel difficult to follow. The problem is often the relationship between parts rather than the quality of any one block.

    Why it happens

    Music offers a useful way to think about structure: a theme returns with variation, tension builds, and resolution gives the earlier material a purpose. The source names piano as one influence on this way of thinking.

    How to work through it

    1. Identify the central idea the system or explanation should carry.
    2. Let each part develop that idea rather than introduce an unrelated direction.
    3. Return to the main idea at the point where the parts come together.

    The solution

    For example, an architecture explanation can introduce one request, follow how each module changes it, and finish with the resulting response. The repeated request gives the reader a thread through the design.

    How to check it

    Try removing or moving one section. If the explanation still works exactly the same way, that section may not be advancing the structure.

    Where this stops

    This is a way to organise thought, not a proof of technical correctness. Interface checks and real requirements still decide whether the system works.

  6. Personal learning

    I can't maintain what I don't understand.

    So I rewrite borrowed code until it fits my mental model. That costs time, and sometimes the original was fine.

    How it works

    The problem

    Borrowed code can work while remaining difficult to change safely. If its structure does not fit the maintainer’s mental model, every edit starts with rediscovery.

    Why it happens

    Understanding the module boundaries and state flow is part of being able to maintain the system. A working black box without a clear contract leaves that work unpaid.

    How to work through it

    1. Trace the inputs, outputs and state before proposing a rewrite.
    2. Draw the important boundaries and explain one real path through them.
    3. Adapt or refactor the smallest part that prevents you from explaining the design.

    The solution

    Bring the code into a model you can reason about. For example, separate a transport adapter from the state changes it triggers, so a future change has an obvious owner.

    How to check it

    Explain where a new feature would go and which existing behavior must remain true. If that explanation still requires reading every file, the boundary may not yet be clear.

    Where this stops

    The source also names over-refactoring as a weakness. An unfamiliar design can be sound; understanding and a small adaptation may cost less than replacing it.