Start Claude Code with a reproducible repair task
Give Claude Code a reproducible TypeScript repair, inspect the resulting behavior, and verify a patch without relying on a confident completion message.

Start Claude Code with a task whose success you can demonstrate outside the conversation. A small TypeScript repair works well when it has a known input, a visible wrong result, and a test command already used by the repository. The agent can help inspect and change the code while you keep a clear acceptance criterion.
The Claude Code overview, retrieved on September 21, 2026, describes a coding tool that can read a codebase, edit files, and run development commands. The workflow below is a proposed practice, not a claim about how long Minh has used the tool or how much time it has saved him.
Define the case before launching the repair
Suppose a hypothetical API normalizes a list of email recipients. It should remove surrounding whitespace, discard empty entries, and preserve the order of the remaining addresses. The reader should be comfortable running a local Node test and reading a TypeScript function. The current helper discards an address containing uppercase characters because it compares against a lowercased allowlist without normalizing the candidate.
Before asking for a repair, decide the intended contract. Are addresses case-normalized for this application’s matching rule? Should duplicates collapse? Is validation responsible for rejecting malformed addresses, or does another layer handle that? Email semantics can be subtle, so avoid turning a narrow application rule into a universal statement about every address system.
For the teaching example, define a simple rule: trim input, match against a lowercased allowlist using a lowercased comparison key, and return the allowlist’s canonical form. Preserve the first accepted occurrence of each address. That rule is specific enough to test without requiring the agent to invent product behavior.
Allowlist: ["a@example.org", "b@example.org"]
Input: [" A@example.org ", "", "b@example.org", "a@example.org"]
Expected: ["a@example.org", "b@example.org"]
The addresses above are reserved example-domain values. They represent fixture data, not real recipients. The test should never send email; it exercises a pure normalization function.
Make the reproduction executable
Put this deliberately defective helper in recipients.ts in a disposable directory. Its boundary accepts an array of strings, including an empty array, and rejects other roots or non-string members. It accepts reusable arrays, not arbitrary iterators. The fixed allowlist belongs to this fixture; callers cannot expand it through the input.
const allowed = new Set(["a@example.org", "b@example.org"]);
export function normalizeRecipients(value: unknown): string[] {
if (!Array.isArray(value)) throw new TypeError("invalid recipients");
for (const entry of value) {
if (typeof entry !== "string") {
throw new TypeError("invalid recipients");
}
}
const result: string[] = [];
for (const raw of value) {
const key = raw.trim();
if (allowed.has(key) && !result.includes(key)) result.push(key);
}
return result;
}
The validation pass can safely precede processing because this contract requires a reusable array. It also rejects an array hole, which iteration exposes as undefined. Silently broadening the parameter to a stream would require reconsidering that two-pass design. No input array is modified and no mail is sent.
Save this independent suite as recipients.test.mjs. Its expected arrays are literal contract examples rather than results computed by the function being tested.
import test from "node:test";
import assert from "node:assert/strict";
import { normalizeRecipients } from "./dist/recipients.js";
const cases = [
[[], []],
[[" a@example.org "], ["a@example.org"]],
[[" A@example.org ", "", "b@example.org", "a@example.org"],
["a@example.org", "b@example.org"]],
[["B@example.org", "a@example.org", "b@example.org"],
["b@example.org", "a@example.org"]],
[["outside@example.org", ""], []],
[["a@example.org", "a@example.org"], ["a@example.org"]],
[["B@example.org"], ["b@example.org"]],
[[" b@example.org ", "a@example.org", "b@example.org"],
["b@example.org", "a@example.org"]]
];
for (const [index, [input, expected]] of cases.entries()) {
test("contract " + index, () => {
const before = structuredClone(input);
assert.deepEqual(normalizeRecipients(input), expected);
assert.deepEqual(input, before);
});
}
for (const [index, input] of [null, "a@example.org", [1],
[undefined], new Set()].entries()) {
test("invalid input " + index, () => {
assert.throws(() => normalizeRecipients(input), /invalid recipients/);
});
}
Create a local package.json containing only {"type":"module"}. With TypeScript available, compile using tsc --target ES2022 --module ES2022 --outDir dist recipients.ts, then run node --test recipients.test.mjs. The local run used TypeScript 6.0.3 and Node 22.23.2 on September 21, 2026. Compilation succeeded; the baseline passed ten cases and failed three. A compiler success therefore did not establish the matching rule.
For a manual reference repair, change only const key = raw.trim(); to const key = raw.trim().toLowerCase();, compile again and rerun the unchanged suite. All thirteen cases then passed. The first mixed-case occurrence now determines output order, even when a later lowercase duplicate exists. This is application-specific allowlist matching, not a general assertion that every email local part is case-insensitive. Unicode mailbox normalization, syntax validity, deliverability and sending remain outside the example.
These are executed, manually authored fixtures. The installed claude --version reported 2.1.278, but no Claude model session generated this patch or ran a timed trial. Use the fixture as a task packet for your own session, then inspect what actually happens. Simon Willison’s 11 March 2025 account of using LLMs for code emphasizes the developer’s responsibility to see the software run; the baseline and unchanged tests put that responsibility into a reproducible form.
Supply a focused task packet
Give the agent the failing test or a command that reproduces the defect, the helper’s location if known, and the behavior contract. Ask it to inspect callers before changing the helper, since a shared function may affect more than one endpoint. Include any repository instruction file that already defines test commands and conventions.
The Claude Code best-practices documentation recommends giving the tool a way to verify its work. A useful task packet therefore includes both the desired result and how to check it. “Improve this function” is too broad for the first repair because it leaves correctness, style, and scope mixed together.
The figure separates the input contract from the acceptance check. The agent may choose a set, a map, or a small loop to implement the rule. Review the outcome and its edge cases before debating those implementation choices.
If the repository has uncommitted work, inventory it first. Preserve unrelated changes and tell the agent which files are already in progress. An isolated branch or worktree can help when several tasks are running, but it does not remove the need to review the final combined diff.
Observe the investigation
The first useful actions are usually reading the existing test, the helper, and the callers. If the agent starts by replacing the entire validation library, inspect whether that scope is justified by the defect. A small reproducible case often needs a small change, though the existing architecture may reveal a broader contract issue.
When a test fails because dependencies are missing, distinguish environment setup from a code failure. A package installation that never completed is not evidence that the patch broke the test. Conversely, a successful install says nothing about the repaired behavior. Keep command results attached to the question they answer.
Let the agent revise its hypothesis when the code contradicts the initial description. The task packet may identify the symptom correctly while guessing the wrong location. What matters is that the final patch addresses the observed behavior and that any scope expansion has a concrete reason.
Check the test’s independence
For the normalization example, tests should state outputs for uppercase input, surrounding whitespace, duplicates, and an address outside the allowlist. They should not call the new helper to compute the expected result. That would make the assertion circular.
A useful regression test fails against the original buggy implementation and passes against the repaired one. You can confirm that relationship by applying the test to the baseline in an isolated checkout, or by inspecting a reliable baseline run captured before editing. Do not casually discard current work just to recreate the old failure.
Read the complete patch after the tests pass. Check that the return type remains stable, that invalid inputs are handled according to the API contract, and that unrelated formatting does not hide a behavior change. If the helper feeds an endpoint, one endpoint-level check can confirm that the canonical addresses reach the expected response or downstream draft.
The final handoff should name what changed, which checks actually ran, and what remains unverified. A claim such as “all tests pass” needs the command and its successful completion, especially if the process was slow or interrupted. For this first task, the finished artifact is a small patch whose behavior you can explain from the input table and whose evidence another developer can reproduce.