Independent IFS Cloud practice · Extensibility & Upgrades
Silent custom code failure in IFS Cloud: the technical anatomy, and how to actually find it
A PL/SQL customisation or Custom Event that breaks with an exception is a bad afternoon. One that keeps compiling, keeps executing, and just silently stops doing what it used to do is a bad quarter, because nothing in the system tells you it happened. This is the mechanics of why it happens after an R1/R2 release specifically, the four concrete patterns that cause it, and the diagnostic sequence I actually run when a client says “this used to work.”
Key takeaways
Key takeaways
- Silent failure is not a platform bug. It is custom code that still compiles and still executes correctly against a data shape or trigger sequence that no longer matches what the release actually produces.
- Four concrete patterns account for most cases: renamed enum/status values, resequenced Custom Event trigger points, a wrapper that swallows a new parameter, and a broad exception handler that was already hiding errors before the upgrade.
- A worked example: a Custom Event gating over-budget purchase orders on a status string comparison silently stops firing the moment the release renames the underlying state value, with zero errors raised anywhere.
- The diagnostic sequence starts from outcome volume, not the code: compare how often the custom logic's side effect actually occurred before and after the release, before reading a single line of PL/SQL.
- Detection has to be built proactively - outcome logging and a scheduled canary test - because nobody notices a rule that went quiet on its own; that is the entire nature of the failure mode.
1.Why custom code breaks silently, specifically after an R1/R2 release
An error is loud because something the platform expects did not happen: a mandatory field was null, a constraint was violated, a procedure call did not resolve. Silent failure is different in kind, not just in visibility. It happens when the platform changes something the custom logic depended on, but not in a way that produces an exception: a status value gets relabeled, a trigger point’s position in the transaction sequence shifts, a procedure gains an optional parameter that a hand-built wrapper never learns to pass. The custom code keeps compiling and keeps running. It simply stops matching anything, or starts running at a point where the data it needs is not populated yet, and every code path it takes from there is technically valid and semantically wrong.
This is why it clusters around R1/R2 releases specifically rather than happening at random. A release is exactly the event that changes enum labels, reorders standard business logic, and adds parameters to existing procedures, all while keeping the outer interface backward-compatible enough that nothing refuses to compile. Extensions built strictly inside the Extensibility Framework, against published Custom Event trigger points and Custom Fields, are far less exposed to this because the framework’s contract is explicitly versioned. Extensions that reach past that boundary into internal package logic or hard-coded status strings inherit every internal change the release makes, with no compatibility guarantee attached to any of it.
2.Four concrete patterns that cause it
| Pattern | What actually changes | Why no error surfaces |
|---|---|---|
| Renamed or restructured enum/status value | A status constant the custom comparison hard-codes as a literal string is relabeled or split into two values in the release | The comparison is syntactically valid PL/SQL; it just never evaluates true again |
| Resequenced Custom Event trigger point | The standard logic around a trigger point is reordered, so the custom logic now fires before a field it reads has been populated for that transaction | The custom code runs successfully against a null or stale value instead of the intended one - no exception, just a wrong branch taken |
| Wrapper that ignores a new parameter | A standard procedure gains an additional optional parameter in the release; a custom wrapper built against the old signature still compiles because the parameter is optional | The new behaviour the parameter controls simply never activates through the custom path, silently diverging from the standard path |
| Broad exception handler | Nothing in the platform changes here - a WHEN OTHERS THEN NULL-style handler was already present, added earlier to survive one specific edge case |
Any new error class the release introduces into that code path is swallowed by the same handler, indistinguishable from the edge case it was written for |
The fourth pattern deserves its own emphasis, because it is not really an upgrade problem at all, it is a pre-existing landmine the upgrade steps on. A handler written to catch one known, harmless exception and continue is functionally identical to a handler that catches every possible exception and continues, from the platform’s point of view. The moment the release introduces any new failure mode into that code path, whether related to the original edge case or not, it disappears into the same catch block with the same silent continuation.
3.A worked example: the budget-gate Custom Event that went quiet
Illustrative scenario, not a claim about a specific real trigger point or status value [VERIFY against your own customisation]. A Custom Event fires on purchase order line approval, checks whether the line’s value exceeds a budget threshold, and if a status field on the order reads a particular value, blocks the approval and routes it to a manager instead:
| Before the release | After the release |
|---|---|
| Status field holds the literal value the custom comparison checks for | The release restructures the status enum; the same business state is now represented by a different literal, or split across two fields |
| Comparison evaluates true for over-budget lines, event blocks and routes them | Comparison never evaluates true against the new literal; the event executes every time but takes the “no action” branch every time |
| Manager sees a routed approval whenever a line exceeds budget | Every over-budget line auto-approves silently; nothing errors, nothing logs, nothing routes |
Nobody notices for months, because the failure produces exactly the same visible outcome as a correctly functioning system with no over-budget lines that quarter: nothing shows up in the manager’s queue. The gap is only found when someone reconciles actual spend against the budget threshold independently of the control that was supposed to be enforcing it, which is precisely the kind of check that should not have to be the detection mechanism in the first place.
4.The misconfiguration that turns an upgrade issue into an invisible one
Every one of the four patterns above is detectable, in principle, by reading the diff between release versions. What actually makes it invisible in practice is a specific pre-existing habit: custom logic that produces no output, no log entry and no counter increment on either the success or the no-action path. If the budget-gate event in the example above had written one row to a log table every time it evaluated, regardless of outcome, someone would have noticed the “blocked” count drop to zero the week after go-live. Because it wrote nothing on the no-action path, and the no-action path is indistinguishable from correct behaviour when there is genuinely nothing to block, there was no signal to notice at all.
The trap compounds when combined with the broad exception handler pattern: code that swallows errors and logs nothing on any path is, by construction, a black box that only a human explicitly reconciling outcomes against expectation will ever catch. That reconciliation should not be the primary control. It should be the last line of defence behind logging that was designed in from the start.
5.The diagnostic sequence: where to actually look, in order
When a client says “this used to work and I think it stopped,” this is the sequence, deliberately starting from outcome data rather than code, because reading the custom logic first wastes time on code that may be entirely correct against a world that no longer exists:
- Compare outcome volume before and after the release - how often did the custom logic’s visible side effect (a block, a routed approval, a flagged record) occur per week, before versus after. A sudden drop to zero, or a suspicious flatline, is the first real signal.
- Search the custom code for broad exception handlers - any
WHEN OTHERSor equivalent catch-all, and temporarily narrow it or add logging inside it during diagnosis, since it may already be hiding the actual failure. - Diff the Custom Event’s trigger point definition and firing order against the previous release’s standard logic around the same point, checking specifically whether anything the custom logic reads is now populated later in the sequence than it used to be.
- Check every hard-coded literal the custom logic compares against - status strings, enum values, code identifiers - against the current release’s actual values for that field, not the values documented when the customisation was originally built.
- Re-run a known test transaction with explicit logging added at each branch, not just at entry and exit, so you can see exactly which comparison evaluates differently than expected rather than only confirming that the end-to-end outcome is wrong.
- Check any wrapped standard procedure calls for parameters added since the customisation was built, comparing the wrapper’s call signature against the current procedure’s full signature, not just confirming it still compiles.
6.Building detection in, instead of waiting to be told “this used to work”
The diagnostic sequence above finds a failure after someone has already noticed something feels wrong. The better position is designing custom logic so a silent failure cannot stay silent for long:
- Log outcome on every path, not only the action path - a row written on “evaluated, no action taken” is what makes a volume drop visible instead of indistinguishable from genuinely quiet weeks.
- Replace broad exception handlers with narrow, named ones, and log whatever they catch - a handler that only ever catches the one condition it was written for should say so explicitly in the log, so a new error class arriving through the same block is visible as a new event, not silence.
- Add a scheduled canary test - a Custom Event or Workflow that runs a known scenario against a test record on a fixed schedule and checks that the expected side effect actually occurred, alerting if it did not. This catches the exact failure mode above without waiting for real transaction volume to expose it.
- Re-test every custom extension against each release’s upgrade sandbox before go-live, specifically re-running the canary scenarios rather than only checking the code still compiles - compiling cleanly and behaving correctly are two different questions, and the gap between them is exactly what this whole failure mode lives in.
- Keep the customisation footprint inside the Extensibility Framework’s published contracts wherever possible, because that is the boundary IFS actually version-manages; every step taken past it into internal logic is a step outside anyone’s compatibility guarantee.
Clients running this kind of structured, canary-backed regression approach across their customisation footprint report up to 60% less regression testing effort per release, because the testing is targeted at outcomes known to matter rather than a full manual click-through of every custom screen and rule after each upgrade.
7.Frequently asked questions
Why does custom PL/SQL code fail silently instead of throwing an error?
Because the platform change it depends on is usually not a broken interface, it is a changed meaning: a renamed status value, a reordered trigger sequence, or a wrapper that never learns about a new parameter. The code stays syntactically and semantically valid PL/SQL, it just stops matching the data it was written against, so it compiles, executes, and quietly takes the wrong branch every time.
Why does this cluster around IFS Cloud R1/R2 releases specifically?
A release is exactly the event that relabels enum values, reorders standard business logic around trigger points, and adds parameters to existing procedures, while keeping enough backward compatibility that nothing refuses to compile. Extensions built strictly against the Extensibility Framework's published Custom Event trigger points and Custom Fields are largely insulated from this, because that contract is explicitly versioned. Extensions that reach into internal package logic or hard-coded literals inherit every internal change with no compatibility guarantee.
What is the first thing to check when a custom rule seems to have stopped working?
Compare the outcome volume before and after the release, not the code. How often did the rule's visible side effect - a block, a routed approval, a flagged record - occur per week before the release versus after. A sudden drop or flatline is the signal that something changed; reading the custom logic is the second step, not the first, because it wastes time confirming code that may be entirely correct against data that no longer exists in that shape.
How do you stop a silent failure from staying silent next time?
Log the outcome on every code path, including the "evaluated, no action taken" path, replace broad exception handlers with narrow, logged ones, and add a scheduled canary test that runs a known scenario and checks the expected side effect actually occurred. This turns a silent failure into a logged, alertable event instead of something only a human reconciliation happens to catch months later.
8.About the author
Dariusz Myśliwiec - 25+ years in ERP and supply chain, 17+ on IFS (Apps 7.5–10 and IFS Cloud). IFS Certified Associate Consultant. PRINCE2® 7. Based in Kraków, delivering remotely across Europe and globally through an independent practice.
Selected clients: Fugro · LGC · BVI Medical · Betafence (PRÆSIDIAD) · Barlinek · NGK Ceramics · Newag · Oleofarm.
IFS is a registered trademark of IFS AB; this practice is not affiliated with IFS AB.
Find out which of your custom rules already went quiet
Before your next R1/R2 upgrade, I can run outcome-volume checks across your custom Events and PL/SQL logic and tell you which ones stopped doing what they were built to do, and when.