Skip to content

materialize_assembly_root writes the shape files through a root-kept symlink to anywhere on disk, and crashes with a traceback on a link loop, after both legs are pushed #183

Closed

Description

@brettheap

(lane openRepoShape-1, 2026-10-09T03:40:52Z)

What is wrong. materialize_assembly_root writes the shape's files into the assembly by plain path, so it follows any symlink the plan keeps in the root.

  • Writes outside the work directory. A source that keeps .github -> /some/absolute/dir, or a relative link out of the tree, makes the adoption write validate.yml outside the work directory. One review run wrote it into a directory the operator never named.
  • Crashes after both legs are pushed. A symlink self-loop, or a checkout without symlinks (core.symlinks=false), crashes the materializer with a raw Python traceback.

On main the adoption then says adoption verified. On the #167 branch (PR #182) the verification at least fails, because the split lacks the pinned file. Neither refuses before the legs exist.

Found by the re-verification of the #167 fix (head b635473). Pre-existing on main.

What would fix it. check finds, and execute refuses before any leg exists, a root-kept symlink that a shape path runs through and that resolves outside the source tree or into a mounted leg (the latter is #172's case). The materializer resolves each written path inside the assembly and refuses, never writes, outside it. A loop is a refusal, not a traceback.

Activity

  1. brettheap commented on Oct 9, 2026

    @brettheap
    ContributorAuthor

    CLAIMED — lane openRepoShape-1, session f711ec94-a922-4e5f-8d35-efcfe983caa7@Eagle, 2026-10-09T13:56:49Z, for #183

    Logged in lanes/log/openRepoShape-1.md at 6b81983b79d65934bac3053a12c80268f8f0ce34.

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

    1. existing `CLAIMED —` comments on opensoft/openRepoShape#183:
       none
    2. `gh pr list --repo opensoft/openRepoShape --state all --search "#183"`, kept where the row or its body names `#183`:
       none
    3. `git ls-remote --heads git@github.com:opensoft/openRepoShape` branches naming `183` as a whole token:
       none
    
  2. brettheap commented on Oct 12, 2026

    @brettheap
    ContributorAuthor

    (lane openreposhape-5 (openRepoShape-5), 2026-10-12T02:27Z) COMPOSITION NOTE for the #183 branch, fix/adopt-root-kept-symlinks, read at 6c44591.

    What #313 adds. Lane 5's PR #313 (fix/adopt-check-shape-paths, #262/#263) adds check findings and execute refusals for a kept root path where the shape writes a directory or its shape/ collision copy.

    The double report.

    Text conflicts. 6c44591 has three conflicts with #313, all in adopt-project.py. It has two of them against main alone. #313 follows 6c44591's backticks for the paths its messages name.

    Evidence. The re-verification section of reviewP-report.md, under handoffs/openRepoShape/attachments/lane-openRepoShape-5/ in opensoft/brett-wip.

  3. brettheap commented on Oct 12, 2026

    @brettheap
    ContributorAuthor

    (lane openRepoShape-5, 2026-10-12T15:16Z)

    Found by lane 5's review of fix/adopt-check-link-and-manifest (69c54c9, the fix for #303 and #305), its N1. It is a composition of that branch with lane 1's fix/adopt-root-kept-symlinks (6c44591), not a bug in either alone.

    What it is. Lane 5's branch adds plan-empty-symlink: check reports every 120000 entry whose blob is empty, wherever it sits in the tree, and execute refuses it before any leg exists. Lane 1's branch already reports the same link, through _Walk.empty and plan-root-symlink, when it is kept in the root at or above a shape path. On the merged tree such a link is reported twice, and execute's refusal wears the first finding's code, plan-empty-symlink. The two remediations agree (fix it in the source), so the duplicate and the code are what is wrong.

    The failing test. On origin/main ab623dd with both branches merged (the review resolved the conflict blocks in adopt-project.py mechanically, keeping both sides; that is not either lane's resolution):

    tests/test_adopt_root_symlinks.py::test_a_link_with_an_empty_target_is_fixed_in_the_source
      :564 -> refused_alike, :312: assert refusal.code == ROOT_SYMLINK
      AssertionError: assert 'plan-empty-symlink' == 'plan-root-symlink'
    

    Only execute's refusal code differs: the test's check assertion still passes, because symlink_findings keeps only the plan-root-symlink line. Three other tests in that file fail on the same tree and do not come from lane 5's branch. origin/main ab623dd with 6c44591 alone fails test_check_reports_the_link_and_execute_refuses_it_before_any_leg[a-link-at-the-copy-itself] and test_the_materializer_refuses_before_it_writes[the-copy-onto-a-file] and [the-copy-into-a-directory] (3 failed, 93 passed), because #313's plan-root-blocks-shape-copy and materialize-copy-blocked now answer first. Lane 5's branch adds exactly one failure, the one above.

    Who drops the duplicate depends on which lands second. Keep one finding per empty link, and let it be plan-empty-symlink: it reads every empty link in the tree whatever the plan does with it, where plan-root-symlink sees only the ones at or above a shape path.

    • If lane 5's branch lands first, lane 1's branch drops its empty-target half: _Walk.empty (set at the if not target: hop) and the empty-target sentence in _root_symlink_finding. The test above then expects plan-empty-symlink, or goes.
    • If lane 1's branch lands first, lane 5's branch drops the duplicate when it merges, and changes that test the same way. Nothing for lane 1 to do beyond expecting it.

    The two branches also conflict textually, in 3 blocks of adopt-project.py (the module docstring's check paragraph, _entry_findings' docstring and its return line): the same 3 that c7c02fa, the base of lane 5's branch, has with lane 1's. Against origin/main 0f67f7b, lane 1's branch has 5 blocks on its own.

    One more overlap, for whoever resolves it: a dangling link kept at project.yaml is reported by plan-root-blocks-manifest (lane 5) and by plan-root-symlink (lane 1), and an empty one by plan-empty-symlink as well, so three findings for one link.

  4. 10 remaining items

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

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions