From inherited code to a documented, tested, load-tuned system in about five weeks
A case study in using Claude Code on a legacy web application
At a glance
| Situation | One engineer inherits two legacy PHP web applications for a client, with no handoff, no documentation and no automated tests |
| Approach | Claude Code, directed in plain conversation, operating the environment, reading the code and verifying findings live |
| Window | About five weeks from first to last session: 2 sessions over 5 active days, about 8 hours of active session time in total |
| Effort from the engineer | 33 typed messages, about 600 words in total |
| Output | About 26,000 words of documentation, 44 passing end-to-end tests, a load-tuned runtime configuration and a ranked review of risks |
The situation
An engineer took over support of a long-running web application used by many external users to submit documents and by staff to review them. A second, sibling application handled a related workflow and shared the same database design. Both were built on an older PHP framework, both ran against a large database, and neither came with a handover pack. There was no runnable local environment, no written description of the roles and screens, and no test suite beyond the framework's own harness.
The goal was modest and urgent: understand the systems well enough to support them.
How the work was run
The engineer set one working rule from the start: stay in the conversation. No separate terminal sessions, no copy-pasting commands. Claude Code was asked to do the work directly, and to prove it.
- Read and map. Claude Code read the codebase and wrote a project guide describing the request flow, the domain model and the places where behavior branches by user role.
- Make it run. It built a containerized local stack and imported an authorized copy of the database, all from the chat.
- Look at it as users do. It logged into the running application in a real browser as each kind of user in turn (an administrator, staff, an external user), clicking through the main pages. Several behaviors that are hard to see in code turned up only this way, such as permissions that behave differently depending on a user's role.
- Write it down. From those walkthroughs came a user manual and a developer guide, each with screenshots, then a comparison against the client's existing manuals that listed the gaps.
- Review risks. A structured review produced findings ranked by severity. The details are deliberately not published here.
- Test it. A test plan was organized in tiers so the team could choose what to run by the time available, backed by a pytest and Playwright suite.
- Make it hold up. Production slowness was traced to configuration, not code, and the fixes were measured.
Timeline
| When | What happened | Typed messages | Claude responses |
|---|---|---|---|
| Day 1 | Code read, project guide written, local stack running, database imported | 2 | 38 |
| Day 2 | Role walkthroughs, manuals, comparison, risk review, test plan | 18 | 488 |
| Days 16 to 19 | Sibling system brought up and documented, test suites built, slowness diagnosed and load tested | 11 | 683 |
| Day 36 | Short handoff review | 2 | 5 |
The numbers
Counted from the saved session transcripts, through the end of the engagement window:
| Measure | Value |
|---|---|
| Messages typed by the engineer | 33 (about 100 characters each) |
| Claude Code responses | 1,214 |
| Tool actions (commands, reads, edits, browser steps) | 1,363 |
| Active session time | about 7 h 55 m |
| Responses per typed message | about 37 |
| Words of documentation produced | about 26,000, against about 600 typed |
The densest session was the one that stood up the sibling application and tuned both: 8 typed messages produced 664 responses.
The slowness: three defaults
Under heavy load, both applications dropped requests. The causes were three configuration defaults, not application code:
- The PHP process pool was left at its smallest default, so only five requests could be served at once.
- Two layers of the web stack disagreed about how long to wait for a response, so requests died in the gap.
- The bytecode cache was never installed, so the whole codebase was recompiled on every request.
After the fixes, dropped requests in the load tests fell to zero and median response times fell several-fold. The pool settings were made configurable per environment, and worker counts were sized from measured per-worker memory against the production hardware profile.
A caveat the team put in writing: these measurements came from a development machine, not production hardware. They show the cause and the direction of the fix, not a guarantee, and a repeat on the real target was listed as an open item.
Where it was wrong, and how that was caught
The most useful part of the process was how often a first answer was checked and changed:
- A screen that looked mislabelled turned out to be correct once the client's own manual was read, and the documentation was fixed instead of the finding.
- A project note stating that a framework feature was switched off was verified at runtime, found to be switched on, and corrected.
- Claude Code once reported that a guide had been updated when the edit had not happened. The engineer asked, a search showed the file unchanged, the edit was made, and "never claim a file changed unless the edit actually happened" was saved as a standing rule.
Each correction was recorded, and the lessons were saved to the project's memory so that later sessions started from them.
What was left open
- The load tests needed repeating on production hardware.
- About 88 scaffolded tests were still waiting for real assertions.
Takeaways
- Operate, don't just advise. The biggest gain came from Claude Code running the environment itself, which made every later step verifiable.
- Verify live before you write it down. Findings tested against the running system were more accurate than findings inferred from code, and the corrections were cheap to make early.
- Documentation is a by-product of the work. The manuals, guides and test plan came out of the walkthroughs rather than after them.
- Write the caveats down. The open items and the hardware caveat made the results more credible, not less.
- Keep the thread. Saved project memory meant a status check weeks later could report what had changed and what was still open without re-reading everything.
About these numbers
Message, response and tool-action counts come from the session transcripts and cover the engagement window only. "Active session time" adds up gaps between events of 30 minutes or less. Performance figures are from a single development machine and should not be read as production results.