Skip to content

update-shape.py: apply rolls back an OSError after its first write only where the system refused a read, so a file gone between the stat and the read, or an error naming no path, ends in a traceback with the tree half-written #1173

Closed

Description

@brettheap

Goal: update-shape.py apply puts every byte it wrote back whichever OSError ends it after the first write, as it already does for a Refusal, a CommandFailed and a read the system refused.

Filed by lane openRepoShape-2, from rv976's independent review of #976 (#430 slice B) at 59b12500d (#976 issuecomment-6278180589), its NIT 1. #976 landed as 36e39a0; this is the limit its body names and the review accepted, as its own issue because it changes how the tool answers errors other than #430's. Line numbers are main 038dafba9's. This was drafted at main dc7cd49b7 (2026-10-20); every line cited reads the same at 038dafba9, and by grep no test added since holds this case.

cmd_apply's OSError arm rolls back one kind of OSError

State. At main 038dafba9, cmd_apply (update-shape.py:5125) rolls the ledger back in three arms (:5195-5205):

The write steps are covered another way: Rollback.writing (:3178) turns any OSError inside one into update-write-failed, a Refusal, so the first arm rolls it back (_write_copies, _extend_the_eol_block and _repin run inside it). What runs after the first write and outside one is not:

What happens. rv976's own example: a file gone between the stat and the read, after the first write. By that review, "only a read the system refused after the first write is rolled back. Any other OSError there still escapes with the tree half-written." By reading (not run for this draft), the same holds for an OSError that names no path, such as a BrokenPipeError from one of those flushes when the output is a closed pipe, and for an interpreter or a working directory that is gone when the validator starts (a FileNotFoundError, which _says_absent reads as nothing there): unreadable_refusal answers None, :5204 re-raises, and the ledger still holds every write. The copies and the re-pin are on disk, the root's own validators have not decided, and no line says what was written: the outcome Rollback.writing's docstring (:3178-3192, #285) and _rewrite_readme's (:5001-5025, Copilot on #152) call a traceback with the writes left in place. Before #976 every OSError after the first write escaped, so #976 narrowed the gap and did not close it.

Evidence.

  • rv976's NIT 1, and its ruling: "accept it as the known limit the body names ... The fix is to roll back on any OSError and then re-raise."
  • The arm as it stands, update-shape.py:5197-5205; tests/test_update_shape_unreadable.py:626 (test_any_read_refused_after_the_writes_rolls_them_back) holds only the refused read, with a PermissionError that names a path.
  • Rollback.undo (:3243) returns None when nothing was written or when the commit landed and is the result, so rolling back on any OSError after the commit puts nothing back.

Direction.

Related: #430 (the sweep), #458 (the tracker for this tool), #428 and #285 (the rollback's earlier rounds).

Size: S.

Lane: openreposhape-2 (openRepoShape-2)

Activity

  1. brettheap commented on Oct 22, 2026

    @brettheap
    ContributorAuthor

    CLAIMED — lane openRepoShape-3, session b30e8b49-f249-4668-adb1-1dbfa93c09e1@Eagle, 2026-10-22T21:07:42Z, for #1173

    Logged in lanes/log/openRepoShape-3.md at b5d1854d5e7eb775fb1c750f21cd52a73fdf3b36.

    The three reads (lane-collision-protocol Rule 1):

    1. existing `CLAIMED —` comments on opensoft/openRepoShape#1173:
       none
    2. `gh pr list --repo opensoft/openRepoShape --state all --search "#1173"`, kept where the row or its body names `#1173`:
       none
    3. `git ls-remote --heads git@github.com:opensoft/openRepoShape` branches naming `1173` as a whole token:
       none
    
  2. added this to the v1.2.1 milestone on Oct 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions