Skip to content

Ship your first reviewed change with OpenCode

Now the actual ticket. feedmill pulls dozens of feeds and stitches them into one reading queue, and one source has been misbehaving: items from it land with the wrong timestamp, so they sort into the wrong place in the queue and you read week-old posts as if they’re fresh. From the last lesson you know the parsing lives in the parser package and that one source’s dates plainly don’t match what the parser expects. The likely cause is the oldest one in the book — the parser assumes a single date layout, and this feed writes a different one. You don’t need to hand OpenCode any of that. You describe the outcome you want the way you’d say it to a teammate, and let the loop you just watched do the legwork.

Set the checkpoint before you describe the work

Section titled “Set the checkpoint before you describe the work”

There’s one thing to do first that the Codex and Claude Code worlds don’t make you think about, because OpenCode’s default is different: out of the box the build agent runs with edit set to allow, which means it writes edits to disk without stopping to ask. For your first change in an inherited repo, you want the pause — an approval prompt on every edit, but one that still applies the change once you say yes.

The cleanest way to get that is one line of config. Set permission.edit: "ask" in opencode.json (or in your global config), and every edit the build agent wants to make turns into an approval prompt: it shows you the diff, waits, and writes only after you approve.

opencode.json
{
"permission": {
"edit": "ask"
}
}

Don’t reach for the plan agent here, even though its name sounds right. Plan is OpenCode’s read-only mode — it ships with edit set to deny (its own description is “Plan mode. Disallows all edit tools”), so it can analyze the repo and propose changes but won’t apply them at all, even with your approval. It’s the right tool when you want a plan and nothing written; it’s the wrong tool when you want to actually land this fix. You’ll switch to it later in the course; for your first change you want build with edits gated to ask. (You cycle primary agents with the agent_cycle keybind, Tab by default, but you don’t need to switch agents for this — the config does the work.)

> one feed source is landing items with the wrong timestamp — looks like
the parser only understands one date layout and this feed uses another.
Make the date parsing handle the formats our feeds actually use, and
add a test that pins the case that's broken right now.

Notice what you didn’t say. You didn’t name the function, didn’t paste the parsing code, didn’t spell out the test framework. You stated the what and left the how to the agent — and it works the problem the same way you would:

| Read parser/date.go, parser/date_test.go (2 files)
The parser pulls each item's date with time.Parse against a single
hardcoded layout, so a feed using a different one is silently parsed
wrong (or zeroed). I'll have it try the layouts our feeds use in order
and return an error if none match, then add a test for the layout
that's currently failing.
| Edit parser/date.go
- t, err := time.Parse(time.RFC1123Z, raw)
+ t, err := parseFeedDate(raw)
Apply this change?
Yes No Always

It read the relevant files first — including the existing test, so its new test would match the house style instead of inventing one. It proposed a plan in two sentences before touching anything. Then, because you gated edits to ask, it stopped at the edit and asked. That pause is the leash. The approval dialog shows you the operation, the file path, and a unified diff of exactly what it wants to write — you’re approving a concrete change, not a vague intent. The Always option would stop asking for edits like this for the rest of the session; resist it on your first change. Approve this one edit, it moves to the test, asks again, and then does the thing that actually closes the ticket — it runs the project’s tests itself:

| Edit parser/date_test.go → approved
| Bash go test ./parser/
ok feedmill/parser 0.214s
Done. The parser now tries the feed date layouts in order and returns
an error when none match; the new test covers the timestamp that was
previously parsed into the wrong day.

There’s the full loop, closed on a real bug: read, propose, approve, apply — with the apply beat ending in the agent verifying its own change against the test suite before it reports done. OpenCode runs the test through its bash tool, which is gated by the same permission model as edits, so you see (and can approve) the command it runs.

Read the diff before you believe the summary

Section titled “Read the diff before you believe the summary”

The agent’s closing summary is a claim about what it did. The diff is the evidence — and on a parser-heavy repo you inherited, feeding a public reading queue, the gap between claim and evidence is exactly where you want your eyes. Read it:

> show me everything you changed

You’re reading for two things: does the change do what I asked, and did it do anything I didn’t ask. For a date-parsing fix this is a thirty-second read, but the point isn’t the time — it’s that you never skip it. Look closely at the things that bite in this domain: a test that passes because it asserts the wrong day, a new layout that also matches a date it shouldn’t and quietly mis-parses a second feed, or a “fix” that swallows the error where the old code surfaced it. (feedmill is a typed Go codebase, so if you’ve got the language server wired in, OpenCode’s LSP integration will already be flagging anything that doesn’t compile or type-check — but that catches broken code, not wrong code. The wrong-but-valid edit is still yours to spot in the diff.)

If the review turns up something off, OpenCode gives you a quick way back: /undo. It’s git-backed — the docs describe /undo as removing the most recent user message, all the responses after it, and the file changes from that exchange, with /redo restoring them if you change your mind. (It needs the project to be a Git repository; feedmill is.) When it works, the move on a wrong change is clean: /undo, re-describe what you actually wanted, and let the loop run again rather than stacking a correction on top of the agent’s mistake.

Treat /undo as a convenience for the last step, not as your real safety net — that role belongs to Git itself (next section). Its file-reverting behavior has a string of open reliability reports, including cases where it rolls back the message but leaves edits on disk, and where an external version-control undo gets re-applied by OpenCode’s file watcher.

When the review passes, commit immediately — and you can have the agent do it through its bash tool:

> commit this with a sensible message
| Bash git commit -am "Parse multiple feed date layouts; fix wrong timestamps" → approved

This is the single most valuable habit in the chapter. /undo is at best a shallow convenience — it reaches back over the recent exchange, when it reaches at all. Git is the deep, reliable one. A commit is the floor that doesn’t vanish. Every time the agent reaches a genuinely working, reviewed state, a commit locks it in, and then the next experiment — however badly it goes — can never cost you more than the work since your last commit. Commit at every green, reviewed checkpoint and you can let the agent take bigger swings, because the downside is always capped at “go back to the last commit.” On a week of work in someone else’s parsers, that floor is what lets you move fast without fear. Think of it as two tiers: /undo for the last bad step, git commit for everything you’ve already proven good.

Your first change is described, proposed, approved, applied, reviewed, and committed — the whole loop, closed once by hand on a real bug, with a commit locking it in underneath. Now make it something you reach for without thinking, by putting OpenCode where you already spend your day: next to your editor.