The upload was failing intermittently, which is the worst kind of failing because the first few test cases look fine and you trust the thing right before it betrays you. 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 an 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 surfaces only when a form post and an upload race on the same page, which is why it stayed intermittent and looked like an upload bug.
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.
The page-level handler was the one that had to move. Renaming its own parameter from ?action=create to ?view=create freed action for the upload handler alone, and the intermittent failures stopped.
the filename was a path separator
Files were saving as orgeventHeader.jpg instead of org/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 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.
the parent context that wouldn’t let go
The creation function that provisions a new tournament inside the production app had, at some point, its parent-context initialization inlined into the creation flow, and that inlining broke. After the function ran, the redirect sent staff to the parent org’s admin view instead of the newly-created entity’s view. The new record 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 entity’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 an 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 this codebase 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 creation function’s 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.
Legacy debugging is contract archaeology. The widget where the failure shows up is almost never where the contract broke. The upload fix was not in the upload handler; it was a rename that vacated a namespace two layers had shared without knowing it.