On April 16, the upload was failing intermittently, which is the worst kind of failing because the first few test cases look fine. The fault was ?action=, a querystring key that the page’s main form handler and the AJAX upload handler had each claimed independently. When a form post and an image upload arrived close together, IIS resolved the ambiguity in the upload’s disfavor.
This was one long day inside the Create Event flow for HomeTeamsONLINE, a 25-year-old ASP-classic application. The work started as a styling pass on the tournament event creation page and turned into a full archaeology dig once the weird failures started surfacing.
the surface was fine, the contract was not
The ?action=create handler processes form submissions. The ?action=upload handler processes image uploads from the same page. In a modern app with actual routing, a framework catches this conflict at startup. In ASP-classic, with Request.QueryString("action") checks spread across the same file, there is no framework to complain. The collision manifests as an intermittent upload failure on a page where both handlers are in play.
Claude chased the symptom competently. It ran through logs, patched the handler, grepped across the ASP files for anything else that touched ?action=. On a first read the agent’s pass looked clean. What it could not surface was that ?action= was a shared namespace that two layers had entered without knowing about each other. That fact is not in any comment or test. It lives in the implicit contract between two parts of a file that grew incrementally over years, and the only way to find it is to hold the question “are there other callers” in your head while reading code that never asked that question of itself.
Moving from ?action=upload to ?action=uploadFile cleared the collision. The intermittent failures stopped.
the filename was a path separator
Files were saving as UNeventHeader.jpg instead of UN/eventHeader.jpg. Not a filename validation bug. Not a wrong value in a config field. A missing slash in the path construction code caused the UN directory prefix to collapse into the filename, so the file landed in the wrong location with a name that looked like a corrupted string. The fix was one character. Finding it took longer than it should have because I was reading the filename handling code instead of the spot where the directory and filename strings got joined.
Legacy bugs land at the last place that touched the output. The actual fault is upstream, in the code that assembled the wrong input, and those two locations are rarely adjacent in a large ASP file.
MakeHQ and the parent that wouldn’t let go
MakeHQ is the function that creates a new tournament as an HQ league inside HomeTeamsONLINE. At some point its parent-context initialization got inlined into the creation flow and broke in the inlining. After MakeHQ ran, the redirect sent staff to the parent org’s admin view instead of the newly-created league’s view. The new league existed and was correctly formed. The redirect was reading from a context that still pointed at the parent.
Two faults compounded. The parent-org context was leaking into the new league’s initial setup, and the post-create redirect read from that polluted context. Fixing only the redirect would have produced a system that worked most of the time, which in a legacy codebase is often harder to diagnose than one that fails clearly and consistently.
local dev was lying about /photos/
Images serve from the application root on local dev and from a CDN on production. The /photos/ path was hardcoded in enough places that local tests passed while production quietly served broken image references. Environment-aware path resolution is not a new idea, but in a 25-year-old codebase with no central config layer it shows up as a manual grep pass every time someone touches image handling. I have fixed this specific class of problem in HomeTeamsONLINE before, and the fix never fully sticks because nothing enforces it.
what the agent could and couldn’t hold
Claude was useful for velocity. Grepping across the codebase, tracing the MakeHQ call chain, surfacing which files referenced ?action=, drafting the environment-aware path logic for /photos/. An agent that can stay oriented across a large ASP codebase without losing the thread, and doesn’t slow down on iteration nine of a nearly-identical test case, is a real asset when the volume of material to read is the main friction.
What Claude couldn’t supply was the system-level invariant. The premise that ?action= is a shared namespace isn’t in a comment, a test, or any document the agent can read. It is a contract implied by the way the system grew, and violating it produces a symptom that looks like an upload bug because that is where the failure is observable. The agent optimizes toward the stated symptom. You have to carry the unstated contract.
The fix for the upload was not in the upload handler. It was renaming ?action=upload to ?action=uploadFile and clearing a namespace that two layers had been sharing without knowing it.
Legacy debugging is contract archaeology. The widget where the failure shows up is almost never where the contract broke.