Skip to content

Course · OpenCode · Living in the TUI

Undo a bad run and outlast the context window

Two things go wrong in a long session, and they go wrong in opposite directions. The first is too much change: you hand the agent a loose instruction - “tidy up the JSON feed parser” in feedmill, the course’s illustrative Go feed aggregator - and it takes you at your word, editing three files when you only wanted one touched, the other two now carrying changes you never asked for. The second is too much thread: you’ve been driving the same session through half a dozen feed parsers, and the context window is filling up under you, the model about to start forgetting the early decisions that still matter. Both are recoverable without leaving the session. This page is the two moves that recover them.

You asked for a tidy-up and got a sprawl. Before you do anything else, see the damage. git diff --stat lists every file the run touched, straight from Git; asking the agent gets you the same list in its own words - an illustrative exchange:

> show me everything you changed in that last run
| Edit internal/feed/json.go (the change you wanted)
| Edit internal/parse/atom.go (you did not ask for this)
| Edit internal/dedupe/hash.go (or this)

The wrong move here is to start patching: “okay, revert the atom.go change, leave json.go.” That stacks a correction on top of a mistake and gives the agent two confusing edits to reason about instead of one clean slate. The right move is to throw the whole run away and re-ask. That’s what /undo is for:

> /undo
⏺ reverted 3 file changes from the last message
⏺ internal/feed/json.go, internal/parse/atom.go,
internal/dedupe/hash.go restored

/undo removes the most recent user message and everything that followed it - the agent’s responses and the file changes it made on disk. It does this through Git: OpenCode tracks the edits from each exchange and rolls them back as a unit, which is why /undo only works in a project that’s a Git repository (feedmill is one, which is the whole reason this is safe). The default keybind is <leader>u (the leader is ctrl+x out of the box, so ctrl+x then u), or you can type the command (as of October 2026; TUI documentation).

Now you’re back where you stood before the bad run, all three files clean, and you re-describe the work more tightly - name the file, scope it down:

> just the JSON feed parser in internal/feed/json.go - only the
date-coercion block. don't touch the other parsers.

If you undo one step too far, /redo walks it forward again, restoring the message and the file changes you just undid. It restores the run as a unit, just as /undo removed it: redo the sprawl and all three edits come back, not only the json.go one. If the json.go edit was the one you wanted, the narrow re-ask above is the cleaner route; the alternative is to redo and then git restore the two files you didn’t ask for, which also discards any uncommitted work of your own in them. Its default keybind is <leader>r (as of October 2026; TUI documentation). Think of /undo and /redo as a single cursor you slide back and forth over the recent history of the session.

The other failure mode is quieter. You don’t hit a wall with a bang; the session just gets long. Six parsers deep, the agent has read dozens of files, run the test suite a handful of times, and all of that tool output is still sitting in the context window taking up room. Left alone, you’d eventually overflow the model’s context and the run would break mid-task.

OpenCode handles this for you. It watches the token count, and when the conversation is about to overflow the model’s context window it makes a summarization call - replacing the detailed history with a concise summary, then replaying the turn that triggered the compaction so the thread keeps flowing. From where you sit, the session just keeps going, lighter than it was. Automatic compaction is on by default (compaction.auto, default true); a separate opt-in lever, compaction.prune (default false), additionally strips old tool output to reclaim more space, and compaction.reserved tunes the token buffer the mechanism keeps in reserve (as of October 2026; config documentation). An illustrative rendering of the automatic moment:

⏺ context window near limit - compacting…
⏺ summarized 142 messages; continuing

To feel why the moment of compaction matters - and what an unsteered summary does to work that lives only in the thread - watch a session in an unrelated repo hit the same wall with its context window x-rayed. The commands wear generic names; in OpenCode the manual move is /compact, the automatic one is the branch where you just keep typing, and the fresh start is /new - which the TUI lists with the alias /clear and binds to ctrl+x n (as of October 2026; TUI documentation). One difference from the widget: its /compact takes a focus instruction, while OpenCode’s /compact takes none and always writes the fixed template described below (as of October 2026; the compact command in OpenCode’s TUI source):

An illustrative session, played back with its context window x-rayed. Watch what the work costs on the right; when the playback pauses, you decide what the session does next - then replay the other choices.

0k / 200k
Fixed overheadRulesConversationFile readsTool outputSummaries
agent session - billing-app
agent cli · new session · repo: billing-app
the window, x-rayed

    Every number is an illustrative order of magnitude, and the commands answer to different names across tools - /compact, /compress, or /summarize; /clear, /new, or a fresh chat. The outcomes are constructed possibilities, not predictions. Choose based on the task and verify what survives.

    What’s worth understanding is what the summary keeps, because that’s the contract you’re trusting a long refactor to. OpenCode writes the summary onto a fixed template, defined in its source as SUMMARY_TEMPLATE, with five sections: ## Objective, ## Important Details, ## Work State (subdivided into Completed / Active / Blocked), ## Next Move, and ## Relevant Files (as of October 2026; compaction.ts in the OpenCode repository). You don’t pick the sections per call; the structure is the deal. The practical upshot for feedmill: a decision you made early - “we standardized all feed timestamps to UTC before dedupe” - survives compaction if it lands in ## Important Details, which is where constraints, decisions, and facts belong, or shows up in the ## Work State record of what’s completed and what’s still active. So the way you make compaction safe isn’t to fight it; it’s to state the load-bearing decisions clearly as you go, so they land in the slots the template keeps rather than getting buried in tool noise that the summary collapses.

    Automatic compaction is the right default, but it’s a default, and sometimes you want the unsummarized history. The case where this bites in feedmill: you’re debugging why one Atom feed’s dedupe is dropping legitimate items, and the answer is in the exact sequence of what the agent tried three steps back - the specific timestamps, the raw tool output, the dead ends. A summary that compresses “investigated dedupe, ruled out timestamp collision” is exactly the detail you can’t afford to lose. For that session you want the raw thread, and you’d rather hit the context ceiling and deal with it deliberately than have it silently summarized out from under you.

    The documented switch is the config key you already met: set compaction.auto to false and automatic compaction stops chasing the token count (config documentation, as of October 2026). Flip it back when the forensic session is done:

    opencode.json
    {
    "compaction": { "auto": false }
    }

    For a single run without touching config, the CLI documentation lists an environment variable, OPENCODE_DISABLE_AUTOCOMPACT (a boolean, “Disable automatic context compaction”), so OPENCODE_DISABLE_AUTOCOMPACT=1 opencode starts OpenCode with automatic compaction off (as of October 2026; CLI documentation). It lasts only as long as that process; the config key is the one to use when every session in the repo should keep its raw thread.

    With auto-compaction off you keep every message verbatim - and you also own the consequence: a long enough thread will overflow, and you’ll need to start a fresh session or compact it yourself before it does. When you want to compress on your own terms, the manual /compact command (alias /summarize, default keybind <leader>c - so ctrl+x then c; as of October 2026, TUI documentation) runs the same summarization on demand. That’s the trade, and it is a context decision: the summary decides which parts of the history the agent still has to think with, so you choose between a lighter thread and an exact one. Leave auto on for the long building sessions where continuity matters more than any single detail; turn it off for the short, forensic ones where the raw history is the work, and reach for /compact when you decide it’s time.

    Between these two moves you can run a session hard without fear in either direction: /undo and /redo slide a bad run back and forth a step at a time, and automatic compaction keeps a long-running thread alive past the point a raw transcript would have broken. That’s the in-session state under control. Next, a different way to split a session’s intent - outlining in an agent that cannot edit before you let it write: Tab between plan and build.