Guide
Guardrail Tests: Refactor Legacy Code Safely
Guardrail tests pin the current behavior of legacy code before you refactor it. Write tests that capture today's inputs and outputs, watch each one fail before it passes, then make the smallest change that keeps the suite green. The suite becomes the spec, so the agent can't silently change the behavior those tests pin.

On this page
- What are guardrail tests for a refactor?
- Why write tests before refactoring instead of after?
- How do I plan guardrail tests for a large codebase?
- Why confirm each guardrail test fails before it passes?
- How do I stop the agent from generating silently-passing tests?
- How do guardrail tests act as the spec for the refactor?
- Which model should plan the work and which should build it?
- How do I close the loop into Confluence and Jira?
- What are the limits of this approach?
What are guardrail tests for a refactor?
Guardrail tests pin the current behavior of a piece of code so a refactor can't change it without you noticing. You identify the current state, write tests that capture it, then make the minimal code change while keeping the same inputs and outputs. They are sometimes called characterization tests: they describe how the code behaves now, not how you wish it behaved.
They matter most on large, idiosyncratic repos where no one person holds the whole behavior in their head. A coding agent has a bias for action and will rewrite a module without hesitation. Without tests that pin the old behavior, you have no proof the new code does the same thing.
As the workshop put it: identify the current state, write tests for that current state, and then do the minimal code changes by maintaining the output of the current state.
A legacy Java path migrated to Go should take the same inputs and return the same outputs, even as it gets faster and more flexible.
This is covered hands-on in Cursor First Hour — 4 short Units, free to read.
Rather do it than read about it? Run 11 interactive Cursor walkthroughs in a simulated editor. Free, no account needed.
Why write tests before refactoring instead of after?
Write them first, while the old behavior still exists to capture. Tests written after a refactor only prove the new code agrees with itself. The old behavior is already gone by then, so they can't tell you whether you kept it. Tests written first turn any drift the refactor introduces into a red test instead of a production incident.
Interactive diagram. Tab through its regions; each focused region shows its detail in the panel below.
Writing guardrails first turns behavior drift into a failing test. Writing them after only checks the rewrite against itself.
How do I plan guardrail tests for a large codebase?
Start in Plan modeA mode that makes no edits: it researches the codebase and produces an editable plan you review before any code changes. Press Enter for the full definition. before you let Agent write any code. Plan mode researches your repo, asks clarifying questions and writes a Markdown plan that you and the agent refine before anything changes. You can hand it a vague, half-formed prompt and use the planning pass to check your assumptions and tighten the scope. Cursor's Plan mode docs say the hard part is often figuring out what change should be made.
Grafana was the workshop's target: an open-source codebase about ten years old, with thousands of contributors. The presenter ran one Plan modeA mode that makes no edits: it researches the codebase and produces an editable plan you review before any code changes. Press Enter for the full definition. prompt: find all the FIXME and TODO comments, rank the top five most critical, write a testing plan that pins current behavior, then migrate to the new functionality without breaking anything else. Plan mode came back with a Markdown plan that listed the items as P0 through P2, each with a problem statement and its own unit-test plan.
Interactive diagram. Step through it with the Next and Previous controls below, or Tab to a region to read its detail.
A Plan mode pass turns a vague prompt into a ranked, test-anchored migration plan before any code is touched.
Why confirm each guardrail test fails before it passes?
A test that has never failed proves nothing. If a generated test asserts the wrong thing, or nothing at all, it passes green and gives you false confidence. The discipline is old and still applies: never trust an automated test you didn't see fail. Make each guardrail fail for the reason you expect, then make it pass.
As one attendee warned: "Never trust an automated test that you didn't see fail. You write tests based on existing code. This is dangerous... You have no proof." A test you have only seen pass can't show you that it would catch a change.
A report from the same session: Cursor sometimes generates tests that log a failure with something like reporter.log instead of calling the framework's fail() method. It can also wrap validation in if or try/catch blocks that swallow the assertion. The test then passes while proving nothing. You only catch this by seeing the test go red first and by reading the generated assertions yourself.
This takes engineering discipline, because no setting fixes it. Define the golden state explicitly: the happy-path input and the output it should produce. Give that pair to the agent as the test's anchor, or write it into the plan and have the agent follow it. Then inspect the generated test code, because Cursor speeds up your process but doesn't check what is under test for you.
How do I stop the agent from generating silently-passing tests?
Apply the workshop's four remedies. Each one changes how you scope the task or check the output, and all four aim to keep the agent honest about failures.
- Keep the rule file lean and specific. A long rule competes with everything else for the agent's attention, and a short one aimed at exactly this problem is more likely to be followed.
- Keep the context window as empty as possible for test work. In a long conversation, rule context can get overwritten as the window fills, and the agent quietly stops obeying it. Open a fresh agent that only writes and runs tests.
- Add a validator, either a skill or a subagent, that runs the test, reads the output and reruns it to confirm a real failure surfaces before the test is accepted.
- On a complex suite, run tests in their own agent instead of mixing them with feature work, so nothing crowds out the verification step.
The presenter's explanation for a rule the agent followed early and dropped later: in a very long chat, rule context can get overwritten as the window fills. For guardrail tests, start clean and give the agent one job.
How do guardrail tests act as the spec for the refactor?
Once the guardrails pass against the old code, they become the contract the new code must satisfy. This is test-driven development adapted to agents. Agents work well when requirements are pinned up front, so a set of passing behavioral tests guides the implementation that follows.
- 1Pin the behavior. Write guardrail tests that capture current inputs and outputs, and confirm each fails before it passes.
- 2Make the minimal change. Refactor or migrate the smallest unit that moves your goal, such as swapping a slow Java path for Go, while keeping inputs and outputs identical.
- 3Verify before you move on. Re-run the guardrail suite, and advance to the next item only while the current tests stay green. The plan sets the order.
- 4Repeat item by item down the ranked P0 to P2 list, so behavior never drifts more than one small step at a time.
Because the suite gates every step, a change that alters pinned behavior shows up as a red test before you move on. On a codebase too large to review by eye, that gate is what catches a silent rewrite.
Which model should plan the work and which should build it?
Plan with a larger reasoning model and build with a leaner, faster one. In the demo, the presenter planned with an Opus-class model and built with one of Cursor's in-house ComposerCursor's own fast coding model, tuned for the editor and priced well below frontier models; the recommended day-to-day model for executing a plan. Press Enter for the full definition. models, which the presenter described as fast thinking models tuned for execution. When the plan is ready, pick the build model and click Build to start writing code.
Check your Cursor subscription before you copy that split. Opus-class models are third-party models, which the Free and Plus plans don't include, and Plus doesn't include Auto.
The presenter's rule: use a larger, more intelligent model to come up with the high-level plan, and a leaner, faster model to do the building.
The larger model earns its cost on the vague planning step. ComposerCursor's own fast coding model, tuned for the editor and priced well below frontier models; the recommended day-to-day model for executing a plan. Press Enter for the full definition. is fast and capable enough to carry out a plan that is already well specified. If your subscription includes Auto, Auto can route this for you instead of you picking a model for each phase.
The workshop named specific point releases for these models. Cursor ships new model versions often, so check the in-app model picker and cursor.com/docs for the current ComposerCursor's own fast coding model, tuned for the editor and priced well below frontier models; the recommended day-to-day model for executing a plan. Press Enter for the full definition. and planning models instead of pinning a version from any guide, including this one.
How do I close the loop into Confluence and Jira?
Connect the Atlassian MCPModel Context Protocol. A standard that lets an AI agent pull in context from outside the repo, like Jira tickets or internal docs. Press Enter for the full definition., and the agent can push the migration plan into a Confluence doc and file Jira stories straight from the plan. The workshop presenter appended a final task to the plan: update the Confluence doc with the migration details. Separately, the presenter asked the agent to turn the TODO comments in the alerting module into Jira stories.
Plan first, then report and ticket. Run Plan modeA mode that makes no edits: it researches the codebase and produces an editable plan you review before any code changes. Press Enter for the full definition., refine the plan, and let its last task fan out the results: a Confluence page for the team and a set of Jira tickets to distribute the work. Anything you could do by hand in Atlassian, you can describe to the agent and wrap as a repeatable skill.
In the session, the Atlassian MCPModel Context Protocol. A standard that lets an AI agent pull in context from outside the repo, like Jira tickets or internal docs. Press Enter for the full definition. hung partway through creating tickets. The integration is useful, but treat ticket creation as best-effort, and confirm the tickets landed instead of assuming the run finished cleanly.
What are the limits of this approach?
Guardrails reduce risk, but they don't remove the need for judgment. Four limits matter.
- Guardrails only protect the behavior you captured. Untested paths can still change silently, so the real safety boundary is how well your tests cover the critical behavior.
- You still have to define the golden state. The agent won't decide what 'correct' means for you, and pinning the happy-path input and output is human work.
- Generated tests need a human read. Inspect the assertions for the silent-fail patterns above before you trust the suite as a spec.
- MCPModel Context Protocol. A standard that lets an AI agent pull in context from outside the repo, like Jira tickets or internal docs. Press Enter for the full definition.-driven reporting and ticketing is convenient but not guaranteed. Integrations like the Atlassian MCP can fail mid-run, so check that Confluence pages and Jira tickets landed.
Cursor speeds up a sound engineering process. You still have to know which behavior you are protecting, and prove that your tests can fail.
Frequently asked questions
What is a guardrail test?
A test that pins the current behavior of code so a refactor can't change it unnoticed. You capture today's inputs and outputs as tests, confirm each fails before it passes, then refactor while the suite stays green. It is a form of characterization testing aimed at safe migration.
Why write guardrail tests before refactoring rather than after?
Tests written first capture the old behavior while it still exists, so drift from the refactor shows up as a red test. Tests written after only check the rewrite against itself, so they can't prove you kept the original behavior.
How do I plan guardrail tests for a huge legacy codebase?
Use Plan mode. Have the agent find the FIXME and TODO comments, rank the most critical, and write a plan with its own test plan for each item. Refine it, confirm each test fails first, then migrate one item at a time, re-running the suite between steps.
Why must I see a test fail before trusting it?
A test that never failed proves nothing. It may assert nothing, or assert the wrong thing, and still pass. Seeing it fail for the expected reason shows it exercises the behavior. AI-generated tests can pass silently by logging instead of failing, or by swallowing assertions in a try/catch.
How do I stop Cursor from generating tests that pass silently?
Keep the rule file short and specific, and start a fresh agent for test work so the rule stays in context. Add a validator skill or subagent that confirms a real failure surfaces, and read the generated assertions yourself.
Which model should plan the refactor and which should build it?
Plan with a larger reasoning model and build with a faster in-house Composer model. Plan in Plan mode, pick the build model, then click Build. Third-party models, such as the Opus-class model in the demo, aren't on the Free or Plus plans, and the in-app model picker shows the current versions.
Sources & last verified
Cursor ships frequently. Last updated October 28, 2026.