Debugging

The bug lived in a file the app never loaded

A PHP OCR parsing fix depended on tracing autoloading to find the live class among duplicate files before changing and deploying the code.

Octacer July 10, 2026
A developer inspecting two nearly identical parser class files on a dark workstation, with one file dimmed as the dead copy and the other marked live by a small green signal.

The bug lived in a file the app never loaded

We chased truncated defendant names through a PHP OCR platform and found two copies of the parser class. One copy was dead — wrong namespace, never autoloaded. Every hour spent editing it would have changed nothing on the live site.

The fix was small. Finding the right file to change was the whole job. Before touching a line, we read the autoload config and the controllers' use statements to confirm which class the running app actually loaded.

The problem

A US law firm's legal-intake platform runs OCR over court notice PDFs to pull structured fields — including the defendant's name. On real records, that name came out wrong. Sometimes it was truncated to the first name only. Sometimes it bled across into the adjacent field.

The stack is PHP on CodeIgniter 4. The parsing logic lives in a court-parser class and an OCR-service class. So the plan looked simple: open the parser, fix the name-boundary logic, ship.

Why the obvious fix didn't work

The codebase held two copies each of the court-parser class and the OCR-service class. Same class names, similar code, two locations.

One pair was dead. It sat under a namespace path that did not match its directory, so CodeIgniter's autoloader never resolved it. The class file existed on disk, looked authoritative, and was never loaded at runtime. Editing it would have shipped a "fix" that changed nothing — and sent us chasing a bug that appeared unfixed no matter what we typed.

What we did

We refused to edit anything until we could prove which copy was live. The decision: treat "which file loads?" as a question with a definitive answer in the config, not a coin flip.

We read CodeIgniter's autoload configuration to see how namespaces mapped to directories. Then we read the real controllers and traced their use statements — the exact fully-qualified class names they import. Both pointed to the pair under the app's Libraries directory. The other pair, with the mismatched namespace, was confirmed dead.

That turned an ambiguous two-hit grep into one file with a name on it. Only then did we touch the parser logic.

// The controller's own import decides which class runs.
// Read this BEFORE editing — not the grep results.
use App\Libraries\CourtParser;      // live: namespace matches directory
// use App\Services\CourtParser;    // dead twin: namespace never resolves

How it works

The path from symptom to deploy was five deliberate steps.

  1. 1

    Investigate

    Investigate. Confirm the symptom live: query the platform's API across all 10,727 hearing records, not a cherry-picked few, and characterize the failure — truncation versus field bleed.

  2. 2

    Confirm Live File

    Confirm the live file. Read the autoload config and the controllers' use statements to identify the one court-parser and one OCR-service class the running app loads. Mark the mismatched-namespace duplicates as dead and leave them untouched.

  3. 3

    Reproduce in Docker

    Reproduce in Docker. Stand up a local environment that mirrors production — PHP 8.1, MySQL 8.0, and the full 10,727-hearing dataset imported — so the bug reproduces on real data and the fix can be verified against it.

  4. 4

    Back Up

    Back up. Before writing to production, copy the live file so there is an instant rollback.

  5. 5

    Deploy

    Deploy. Push the fix through a fresh private repo on a branch, merge to main, deploy over SSH, and validate the live file with php -l to confirm zero syntax errors.

There was one access wrinkle worth naming. The hosting portal's login was gated by a live Cloudflare captcha that can't be automated, so we set up direct SSH to the production server instead, using a cPanel-generated key. The captcha decided the deploy channel.

The diagram below shows the fork that mattered: the dead copy on one branch, the live copy on the other, and every downstream step hanging off the live one.

What broke / what surprised us

The parser fix was the easy part. The full UI end-to-end test in Docker is what surprised us — it surfaced three bugs that existed only in the local setup, not in production.

  • A missing writable cache directory, which the app needs to run at all locally.
  • A baseURL hardcoded to the live domain, so local pages pointed back at production.
  • Upload size limits set too small to accept real court notice PDFs.

Results

We measured the fix the same way we confirmed the bug: against the 10,727-hearing dataset, through the real interface, not a synthetic sample.

  • Correct file, first edit. Confirming the live class from autoload and use statements meant the first change landed on the code the app actually runs — no wasted deploy cycle on the dead twin.
  • Reproduction on real data. The Docker mirror imported the full dataset, so the parser fix was verified against the same records that exposed the failure.
  • Safe deploy. The live file was backed up before the write and passed php -l after it — a syntax-clean deploy with a ready rollback.

Takeaways

  1. Confirm which file loads before you edit. In a codebase with duplicate classes, the autoload config and the controllers' use statements are the source of truth — not a grep, and not the most authoritative-looking file.
  2. Dead code is a trap, not just clutter. A never-loaded duplicate wastes debugging time and can convince you a real fix "didn't work." Verify the runtime path first.
  3. Reproduce on the real population. Confirming a bug against 10,727 live records, then mirroring that dataset locally, beats reasoning from a few examples.
  4. A faithful mirror has its own bugs. Cache dirs, hardcoded base URLs, and upload limits are local-only failures — expect them, and run a full UI test to flush them out.
  5. Infrastructure shapes the plan. An un-automatable captcha rerouted the entire deploy to SSH. Let the constraints pick the channel, then keep the deploy safe with a backup and a php -l check.

Ready to Implement These Strategies?

Let's discuss how to apply these insights to your specific business challenges.

Schedule Consultation