Skip to content

Running UNDO command reverts changes done by hand #55121

Closed as duplicate
Closed as duplicate

Description

@AtomicFS

Description

It is loosely connected to #46783. But it does not delete files, just deletes changes done by hand.

Plugins

only ex-machina.anthropic-auth

OpenCode version

v2.0.26

Steps to reproduce

  1. Ask LLM to read file without making any changes
  2. Make manual change by hand
  3. Run /undo command
  4. Your manual changes were undone

Screenshot and/or share link

No response

Operating System

ArchLinux

Terminal

foot

Activity

  1. opencode-agent commented on Oct 13, 2026

    @opencode-agent
    Contributor

    Thanks for the report. I could reproduce this on v2.0.26, and it still happens on the latest dev (c1615b1), but only when the manual edit happens while the turn is still running.

    Steps (simulated model, git project):

    1. Ask the model to read notes.txt without changing it.
    2. While it is still working on its reply, edit notes.txt by hand.
    3. Once the turn finishes, run /undo.
    4. notes.txt goes back to its original content and the manual edit is gone (see the recording and screenshot below).

    When I made the edit only after the turn had fully finished, /undo kept it.

    The likely cause: OpenCode takes a snapshot of the project at the start and end of each model step. It counts any file that changed in between as changed by the agent, even when you changed it, and /undo puts those files back. This looks like the same underlying problem as #54026 (undo discards changes made outside the session while a step is running), so it will probably be handled together with it. Related: #51360, #44511.

    If your edit was made after the model had completely finished replying, please say so, since that would be a different case from what I saw.

    recording.mp4

    Screenshot

    Reproduction script (drive2.ts)
    // Variant: manual edit made while the read-only turn is still running (between tool call and final reply).
    import { Effect } from "effect"
    import { readFileSync, writeFileSync, existsSync } from "node:fs"
    import { join } from "node:path"
    import { Llm, OpenCodeDriver } from "opencode-drive"
    
    export default OpenCodeDriver.use(
      {
        ...(process.env.OC_DEV ? { opencode: { dev: process.env.OC_DEV } } : {}),
        config: { snapshots: true } as any,
        project: { git: true, files: { "notes.txt": "original line\n" } },
        tui: { recording: true, keypressOverlay: true },
      },
      ({ ui, llm, artifacts }) =>
        Effect.gen(function* () {
          const projectFile = [join(artifacts, "files", "notes.txt"), join(artifacts, "notes.txt")].find((p) => existsSync(p))!
          yield* llm.queue(
            Llm.toolCall({ index: 0, id: "call_read", name: "read", input: { path: "notes.txt" } }),
            Llm.finish("tool-calls"),
          )
          yield* ui.submit("Read notes.txt, do not change anything")
          // Edit by hand while the model is still "thinking" about the second step.
          yield* Effect.sleep(1500)
          writeFileSync(projectFile, "original line\nMANUAL EDIT BY USER\n")
          yield* Effect.sleep(1000)
          yield* llm.send(Llm.text("The file says: original line. No changes made."))
          yield* ui.waitFor("No changes made.")
          yield* Effect.sleep(1500)
          yield* Effect.log(`before undo: ${JSON.stringify(readFileSync(projectFile, "utf8"))}`)
          yield* ui.submit("/undo")
          yield* Effect.sleep(3000)
          yield* ui.screenshot("after-undo")
          const after = readFileSync(projectFile, "utf8")
          yield* Effect.log(`after undo: ${JSON.stringify(after)}`)
          yield* Effect.log(after.includes("MANUAL EDIT") ? "RESULT: manual edit kept" : "RESULT: BUG - manual edit reverted by /undo")
        }),
    )

    Run it with opencode-drive: opencode-drive run ./drive2.ts. It drives opencode from your PATH; set OC_DEV to the path of an OpenCode checkout to run that from source instead.

  2. kilopal commented on Oct 18, 2026

    @kilopal

    Yeah, snapshotting the whole shared worktree at the start and end of a step means any manual save while the model is still running gets swept into the revert diff.

    I ran into this exact issue and ended up building a small Go runtime called msh (https://github.com/msh-protocol/msh) that runs agent shell commands inside isolated shadow git worktrees instead of the live tree. That way you can run msh undo to roll back individual agent commands atomically without wiping out your manual editor changes.

  3. AtomicFS commented on Oct 18, 2026

    @AtomicFS
    Author

    I would be fine with undoing LLM + human changes, but what annoys me that the LLM only read the document, so there are no changes to revert. Only when LLM actually makes some change, only then undo command should actually do something - reverting to the original, even if human made some changes afterwards - that is acceptable to me.

  4. neriousy commented on Oct 20, 2026

    @neriousy
    Member

    Thanks for reporting this and clarifying that the model only read the file! This is the same problem as #54026, where reverting a message can discard unrelated changes in the shared worktree, and we're tracking it there. Please subscribe or add a 👍 there; your read-only example is worth adding too.

  5. Jackdrakes commented on Oct 25, 2026

    @Jackdrakes

    I’d like to investigate and implement a fix for this. I plan to reproduce the race between an active turn, an external file edit, and /undo, then add a regression test ensuring user-authored changes are not reverted. I’ll first inspect the undo snapshot/patch tracking logic and propose the smallest conflict-safe fix.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions