skip to content

When adding timing inside a compiled dependency, what differs between rewriting after the build, at load, or in a live process?

level: seniorimportance: should knowfreq 50%

answer

  1. three moments, one edit
  2. what the file on disk contains
  3. a hook arrives after some units
  4. restart or no restart
  5. later buys reach, loses record

basics

~20 s

Later moments buy reach and lose record. A post-build edit ships in a reproducible artifact; a load-time hook leaves the artifact untouched but misses units already loaded; a live replacement needs no restart and leaves nothing on disk.

solid answer

~40 s

All three perform the same edit; they differ in when it happens and therefore in what is true afterwards. Rewriting **after the build** produces a new artifact that already contains the inserted code: reproducible, checked once, but it must be produced and republished for every upstream release. Rewriting **at load** leaves the published artifact alone and edits each unit as the process takes it in, so selection can vary per environment — but a hook only sees units loaded after it is installed, and the running code no longer matches the file on disk. Rewriting **in a live process** replaces bodies without a restart, which is what you want mid-incident; it is normally limited to bodies rather than declared members, frames already running finish as they were, and nothing records that it happened.

go deeper

for a junior

Hold on to the sequence: the edit can happen when the artifact is built, when the process loads a unit, or while the process is already running.

for a middle

Explain what each moment leaves on disk and what it costs per process start, and say why an artifact edited in the pipeline is the reproducible one.

for a senior

Show the operating consequences: the hook that arrives too late for early units, the live change that disappears on restart, and the drift that silently removes instrumentation.

for a principal

Decide which moments the organisation permits at all, and make the later ones carry an expiry and a written record rather than becoming the normal way to change production.

## One edit, three moments The edit itself is the same in all three cases: instructions are spliced into the body of a method inside an already-compiled unit. What changes is **when** the splice happens, and that choice decides what is true about the artifact on disk, which code can still be reached, and whether anyone can reproduce the result later. ## After the build A pipeline step takes the built artifact — yours or a dependency's — walks it, edits every matching unit and writes a new artifact. From then on the inserted code is simply part of the thing you publish and deploy. - **Buys:** a reproducible artifact; the edit is performed once, not per process start; the result can be inspected, stored and diffed before anything runs; every environment gets exactly the same code. - **Costs:** you now own a derived artifact and must reproduce it for each upstream release; nothing can be turned on or off without a rebuild and redeploy; the published artifact and the one you run are different objects, and only your pipeline knows that. ## At load The published artifact ships unchanged. A transformer hook is installed early in the process and is offered each compiled unit on its way in; it returns either the original bytes or a rewritten version. - **Buys:** the artifact on disk stays byte-identical to what upstream published; what gets rewritten can be decided per environment or per deployment; removing the instrumentation is a configuration change plus a restart. - **Costs:** the hook only sees units loaded **after** it is installed, so anything loaded during early start-up escapes it; the edit is redone on every process start, which is start-up work proportional to how many units match; and the code executing now cannot be read off disk. ## In a live process A process already running is asked to accept a replacement for a unit it has already taken in. No restart, no redeploy. - **Buys:** reach into a process you must not restart — the long-running one that is misbehaving now and would lose its state or its warm-up on a bounce. - **Costs:** replacement is commonly restricted to **method bodies**, not to the declared members of a type, so an edit that would add or remove a member is refused; frames already executing continue under the body they started with; and the process now runs code that exists in no artifact and will vanish on the next restart. ## Side by side | | After the build | At load | In a live process | |---|---|---|---| | What the artifact on disk contains | the edited code | the original code | the original code | | Units already loaded | not applicable | out of reach | reachable | | Restart required to apply | yes, redeploy | yes, restart | no | | Cost paid per process start | none | re-edit each unit | none | | Scope of edit | anything the format allows | anything the format allows | bodies only, typically | | Reproducible later | yes | only with the same configuration | no | ## How to choose 1. **Default to the earliest moment that solves the problem.** Earlier means checked once, recorded once and reproducible. 2. **Move to load time when the published artifact must stay untouched** — because it is shared, signed, or because different environments need different instrumentation. 3. **Reserve in-process replacement for the case that genuinely cannot restart**, and treat whatever you learn as the input to a permanent fix applied earlier, not as the fix itself. ## The failure each moment has Each moment fails in its own characteristic way, and naming the failure is what separates a candidate who has done this from one who has read about it. A post-build edit fails by drifting: the upstream release changes shape, the rule matches nothing, and the artifact ships without the instrumentation. A load-time hook fails by **arriving late**: the unit you care about was taken in before the hook was installed, so it is silently untouched while everything else is rewritten. A live replacement fails by **evaporating**: it works, the incident ends, the process restarts, and the behaviour everyone relied on is gone with no record that it ever existed.

  • Why can a load-time hook miss a unit that the same rule catches when applied after the build?
    The hook only sees units taken in after it is installed, and only those arriving through paths it observes. Anything pulled in during early start-up, or supplied already prepared, never passes it. A post-build pass instead walks the whole artifact before anything runs, so ordering cannot hide a match from it.
  • What makes in-process replacement attractive during an incident and unacceptable as a permanent answer?
    It reaches a process you must not restart, which during an incident is the only property that matters. Afterwards it is unacceptable because the running process corresponds to no artifact: nobody can rebuild it, review it, or tell from disk that it was changed. Bake the edit into an earlier moment once the incident is over.
  • Does rewriting at load remove the need to keep the rule in sync with upstream releases?
    No. The rule still matches a compiled shape, so an upstream change breaks it at load exactly as it would break it in a build step — only later, in every environment at once, and usually without failing anything. Load-time selection changes where the rule runs, not how brittle it is.

saying these in an interview costs you the question

  • Says a load-time hook changes the artifact stored on disk
  • Assumes a hook can reach units the process already took in
  • Thinks a live replacement may add or remove declared members freely
  • Believes a live replacement rewinds calls already on the stack
  • Treats a post-build edit as needing the upstream project's source