hamsa software
← All articles
Engineering7 min read

What the failed rollback revealed

An importer rejects some rows after a change. A synthetic incident record shows how a configuration, an input feature and a failed mitigation can produce a better explanation than “bad deployment”.

Three offset geometric slabs expose different edges, representing code, configuration and input conditions behind a failure.

The first explanation is often the most available one

The importer starts rejecting rows after a release, so rolling the release back is a reasonable thing to try. In this invented incident, it changes nothing. That failed rollback is worth keeping near the centre of the review: the old code is still using the new parser setting, and the troublesome row contains a quoted comma. Removing the unsuccessful action would make the story neater while losing the clue that changes its explanation.

A recovery attempt can teach us something even when it does not recover the service. Here it asks whether changing the code version changes the result, and the answer directs attention towards a condition left in place. Another person reading the incident should be able to follow that turn. Otherwise the final configuration explanation arrives as an answer without the investigation that made it more convincing than the initial deployment theory.

Google’s SRE postmortem guidance emphasises contributing causes, learning and effective preventive actions rather than blaming individuals. [1] For a small team, that need not mean a sprawling document. It means a record honest enough to preserve uncertainty and precise enough that someone can tell what the follow-up work is supposed to change.

Give the incident a mechanism you can inspect

The original teaching incident uses an importer with two modes: a proper CSV reader and a simple comma split. A configuration change selects simple_split. Both the current and previous code versions in the scenario use whichever mode is configured. The first problematic row arrives later: a company name contains a comma inside quoted text. The input and the selected mode interact; the release timestamp alone does not explain that interaction.

The literal row is "North, Ltd",42. Under the agreed CSV format, the name is one field and 42 is another. Python’s CSV reader understands quoting according to its dialect settings. [2] The fixture returns two fields with csv.reader. Splitting on every comma returns three pieces, so the importer’s two-column check rejects it. There is no need to invoke an intermittent network problem to explain this particular failure.

A plain row, North Ltd,42, succeeds in both modes. A genuinely malformed unquoted row, North,Ltd,42, is rejected in both. Those controls matter. Without the plain row, we might think the importer is universally broken. Without the malformed row, we might “repair” the incident by accepting every three-field record and silently assigning the wrong name or amount. The mechanism distinguishes valid variation from invalid input.

The downloadable parser example keeps all three specimens available under both modes. Preserve the rejected malformed row when repairing the quoted one: allowing every extra piece would make the original symptom disappear by weakening the contract. For the incident review, keep the selected mode beside each result. The authored timeline below uses those local behaviours to follow an investigation; a real review would need its own configuration history and affected records to establish whether the same explanation fits.

Keep four kinds of statement separate

At 09:00, the example configuration changes. At 09:05, a quoted-comma row reaches the importer. Those are observations in the authored record. “The new code cannot handle commas” is an early hypothesis. At 09:10, the code is rolled back while simple_split remains selected, and the row still fails. That result weakens the code-only explanation; it does not prove every aspect of the previous release was harmless.

At 09:14, intake is paused to limit further affected imports. That is a mitigation. It changes exposure to the failure while the cause remains under investigation. At 09:20, comparing the exact row under both modes reproduces the distinction. That is evidence of a mechanism. At 09:25, restoring the agreed CSV mode handles the valid quoted row. None of these steps should be renamed “root cause” merely because it appears near the end of the timeline.

The explanation now has a mechanism. simple_split does not implement the agreed quoted-field format, the incoming row exercises that difference, and the rollback leaves the selected mode untouched. The tests had missed the mode/specimen combination. Keeping the same row while varying the mode and code version makes those links inspectable: both code versions reject it under simple_split, so code version alone cannot explain the behaviour. “Bad data” would miss that the row is valid; “human error” would miss the supported configuration that allowed the failure. Neither label tells a maintainer what to change. The exact comma, quoting and effective mode do, without requiring the review to turn every unsuccessful step into part of an inevitable story.

There can still be unanswered questions. Why was the alternative mode available for this source? Which check allowed the configuration change? Did any earlier records parse into two plausible but incorrect fields instead of failing? A review should retain those questions with owners and next steps. Reproduction of one rejection does not establish the full affected population or prove that all previous accepted output was correct.

A rollback is an observation, not the conclusion

Original synthetic incident record. The deployed code is not the only changing condition: a parser-mode configuration and a quoted-comma input explain why a code rollback alone leaves the failure.

Follow the labelled arrows. On a narrow screen, scroll sideways if needed.

A rollback is an observation, not the conclusion09:00 · Parser mode changes. Configuration selects simple_split instead of csv. Both code versions support the selected mode. 09:05 · Quoted-comma row arrives. The first illustrative valid row containing a quoted comma reaches the importer. 09:10 · Code rollback still rejects the row. The earlier code retains the same simple_split configuration; rolling back code alone leaves the condition unchanged. 09:14 · Intake paused. The pause limits further affected imports. It does not establish the cause or repair prior records. 09:20 · Compare parser modes. For "North, Ltd",42, csv.reader produces two fields while simple splitting produces three. 09:25 · Agreed CSV mode restored. The quoted row is handled; genuinely malformed three-field rows must still be rejected. From 09:00 · Parser mode changes to 09:05 · Quoted-comma row arrives: Next recorded observation. From 09:05 · Quoted-comma row arrives to 09:10 · Code rollback still rejects the row: Mitigation tested. From 09:10 · Code rollback still rejects the row to 09:14 · Intake paused: Failure remains. From 09:14 · Intake paused to 09:20 · Compare parser modes: Investigate the effective mode. From 09:20 · Compare parser modes to 09:25 · Agreed CSV mode restored: Restore agreed CSV mode.Next recordedobservationMitigationtestedFailureremainsInvestigatethe effectivemodeRestore agreedCSV mode09:00 · Parser modechangesConfiguration selectssimple_split instead of csv.Both code versions supportthe selected mode.09:05 · Quoted-commarow arrivesThe first illustrative validrow containing a quoted commareaches the importer.09:10 · Code rollbackstill rejects the rowThe earlier code retains thesame simple_splitconfiguration; rolling backcode alone leaves thecondition unchanged.09:14 · Intake pausedThe pause limits furtheraffected imports. It does notestablish the cause or repairprior records.09:20 · Compare parsermodesFor "North, Ltd",42,csv.reader produces twofields while simple splittingproduces three.09:25 · Agreed CSV moderestoredThe quoted row is handled;genuinely malformedthree-field rows must stillbe rejected.
Authored timeline, not a Hamsa or client incident. Downloadable local parser fixture checks the quoted/plain/malformed distinctions; no live rollback, queue replay or outage duration is claimed.

Make the follow-up smaller than a slogan

“Improve testing” cannot tell the team when it is finished. A better action is to add the quoted-comma specimen to the supported-format contract, run it under every permitted importer mode and prevent unsupported source/mode combinations from being selected. Another action can record the effective parser mode and configuration revision with each import. Both follow directly from the mechanism rather than from a desire to add a reassuring number of tasks.

The rollback action needs a more precise improvement than “make rollback better”. Would restoring a reviewed code-and-configuration pair be appropriate, or should the tool show the configuration it will leave behind? Either can fit a real service. What matters here is that the operator can identify the effective combination before assuming the old code restores the old behaviour. That would have made the unchanged simple_split setting visible during this attempt.

Give each action an accountable role, a completion condition and a realistic review point. For the contract case, completion is a checked specimen and a rejected unsupported configuration, not merely a ticket moved to done. For evidence logging, completion includes finding the effective mode for an example import without recovering it from somebody’s memory. The document becomes useful when its claims about improvement can be inspected later.

The follow-up scope needs to address the conditions established by the reproduction. A rewrite may be justified if the design keeps permitting unsupported modes, but a bounded configuration guard and contract specimen may prevent the demonstrated failure more directly. Conversely, a parser patch is incomplete if the same unsupported combination remains selectable. Explain which condition each proposed action removes and how completion will be verified.

Follow-up actions should change demonstrated conditions

The synthetic parser incident links each action to the quoted-comma failure mechanism.

Scroll sideways to see all columns.

FindingSpecific follow-upCompletion evidence
simple_split breaks a valid quoted fieldTest the specimen under all permitted modesSupported format passes; unsupported source/mode selection is blocked
Code rollback retained configurationRestore a reviewed pair or expose the differenceOperator can identify effective code and configuration
Mode unknown for earlier importsRecord parser mode and configuration revisionFind effective mode for an example import
Affected cohort remains uncertainClassify records by write outcome and source/mode windowReplay, correction and unknown effects remain distinct
A code fix and a completed incident recovery establish different things; neither erases unresolved earlier outputs.

Recovery has a population as well as a cause

Before replaying records, identify the affected cohort from the actual time window, source, mode and outcome evidence. A failed-before-write record may be safely eligible for another attempt under the real import contract. A record with an unknown or partial effect needs a different disposition. “The parser is fixed” is not enough to classify every previous attempt as if nothing happened.

Keep recovery counts by justified outcome: accepted correctly, rejected as genuinely malformed, corrected under a reviewed rule or still unresolved. Do not measure success only by the absence of errors after intake resumes. A backlog can disappear through deletion or silent misparsing too. The same exact specimens that explained the mechanism can help inspect whether the recovery path now preserves the intended fields.

Read the review as the person who may encounter a quoted company name next month. They need to find the active mode, distinguish valid quoting from a genuinely malformed row and know which imports remain uncertain. A document mainly explaining who made the unfortunate change will not help them do those things. A specimen and a recoverable configuration history can, particularly when the apparent quick fix has already been tried.

The unsuccessful rollback belongs in the review because it rules out an attractive shortcut for the next operator. Keep it with the literal row and the setting that survived it. They can then inspect the effective code/configuration pair before choosing another action, while the record investigation decides which imports need replay or correction. Restoring CSV mode explains why the valid row now passes; it does not make the uncertain earlier outputs disappear.

Working on a workflow like this? Hamsa helps finance and operations teams connect their tools and handle the exceptions, starting with one workflow.

See the Payment Operations Pilot

References & further reading

Primary sources behind this article. Living documentation may change.

  1. Google SRE: learning from incidents and contributing causes
  2. Python: CSV dialects, quoted fields and reader behaviour
Hamsa Jama

Practical engineering. A human perspective.

More about Hamsa Software and the person behind it. Editorial notes.

A few more things
worth a read.

Back to the reading room
Engineering7 min read

What does £240 belong to?

A readable table preserves relationships between values, row labels, column headings and actions across different ways of reading.

Engineering7 min read

When two calculations agree on the wrong answer

A shadow comparison exposes differences. Independent rule examples explain whether those differences are bugs, corrections or changed assumptions.