Skip to content

OpenHands resolver: owned-path gate on the exact pushed commit, instruction files and aliases refused, driver behind a start-up self-test - #732

Merged
seathatflowsinourveins merged 18 commits into
mainfrom
foundation/resolver-owned-path-gate-20261004
Oct 5, 2026
Merged

seathatflowsinourveins merged 18 commits into
mainfrom
foundation/resolver-owned-path-gate-20261004

Conversation

@seathatflowsinourveins

Copy link
Copy Markdown
Owner

Scope

  • What this PR changes: it adds an owned-path allowlist gate on the exact commit the OpenHands resolver pushes, and enables the public resolver driver only behind a start-up self-test of that gate. The gate refuses:
    • any path outside the brief's owned paths;
    • Git-semantic dot files;
    • instruction-bearing files: instruction basenames, the Codex AGENTS template, the token-lanes carrier blocks, and the adoption/agents/ prefix;
    • symlinks, gitlinks, mode and type changes;
    • case, Unicode (NFC/NFKC), HFS and NTFS aliases of all of the above.
  • Base commit: 3b8f9c8a1b938358d65bf422f61592dc3b89407d
  • Lane: lane:foundation
  • Owned paths touched: blueprints/runtime-workers/openhands/{resolver.py, resolver/push_gate.py, RESOLVER.md, skills/resolver/SKILL.md, evidence/owned-path-gate-fail-first.txt}, docs/decisions/2026-09-28-openhands-resolver-isolation.md, tests/test_runtime_worker_openhands_{push_gate,resolver}.py, manifests/evidence.json (last commit, per the hot-file protocol).

SOTA sources

  • OpenHands/OpenHands at 7bc33009: openhands/resolver/resolve_issue.py and issue_resolver.py#L376, the upstream resolver whose push this gate constrains.

  • OpenHands/software-agent-sdk at fcc102a697874d54a357e36004e02c95040dbdc0: examples/03_github_workflows/01_basic_action/agent_script.py.

  • OpenHands/extensions at bea7a20c59c44ec4dacddac3fc0efe58b9c73880: skills/github-issue-to-pr/scripts/main.py#L595.

  • git/git v2.43.0 documentation, for the Git-semantic files and plumbing the gate reads:

    • Documentation/gitattributes.txt, gitignore.txt and gitmailmap.txt;
    • config.txt, diff-format.txt, git-commit-tree.txt and git-hash-object.txt.

    The gate uses native git diff-tree --no-renames on the exact pushed commit.

  • github/docs at 82fb420ff85c1b3e66035c4f902bac339de3dd2d, for repository instruction-file locations.

  • The repository's own adoption installers at 0e2610d66, for the instruction carriers they install: tools/adoption/install_claude_profile.py, tools/adoption/apply_codex_lane.py and adoption/hooks/claude/token-lanes-subagent-start.py.

Evidence-class table

Claim Evidence class Command / receipt
Gate refusals: owned-path, dot files, instruction files and prefix, aliases, mode, type and symlink local_integration (synthetic Git repositories, native git, unittest) blueprints/runtime-workers/openhands/evidence/owned-path-gate-fail-first.txt (each control red against the prior round, green after)
Driver enabled only behind the start-up self-test local_integration the same receipt; tests/test_runtime_worker_openhands_resolver.py
Three resolver suites pass local_integration coordinator rerun with a writable gitleaks lock: 381 tests, OK (1 skip: PyYAML parity), exit 0

No live resolver, GitHub, model or provider call is part of this evidence.

Local commands run

$ python3 -m unittest tests.test_runtime_worker_openhands_push_gate tests.test_runtime_worker_openhands_resolver tests.test_runtime_worker_openhands -v   Ran 381, OK (skipped=1)   exit 0   [coordinator]
$ python3 scripts/validate.py                    exit 0   [coordinator]
$ python3 scripts/evidence_manifest.py --check   exit 0   [coordinator]
$ git diff --check                               exit 0   [coordinator]

Review

Built by GPT-6.1 Sol through the packaged SDK worker (rounds r1-r5); committed by the coordinator.

  • Claude Opus cross-family reads, all ACCEPT:

    • r1: two reads, with 5 and 4 P2;
    • r2: 3 P2;
    • r3: 2 P2;
    • r4: 2 P2;
    • r5: 0 P2.

    Each round fixed the previous round's P2s, as each later read confirmed.

  • The coordinator's receipt append, its writable-lock rerun requested by the r5 read: a GPT read returned ACCEPT, 0/0.

Recorded residuals (bounded review, 2026-10-05)

  • Transitive standard-library shadowing beside a CI script run by path: carried from r2 as partial. It is a documented residual with a coordinator precondition at RESOLVER.md:91-99.
  • Under adoption/agents/, rename-in, rename-out, mode, symlink, type and gitlink changes are refused by mechanism only: --no-renames turns a rename into delete plus add, and path_refusals runs on every changed path. No native control exercises these cases; the r5 read gives the scratch fixture to add one.

Decision record

docs/decisions/2026-09-28-openhands-resolver-isolation.md

Checklist

  • No GitHub Actions or workflow changes.
  • No secrets printed, logged or committed.
  • No new paid hosting, subscription or billing surface.
  • Peer-owned untracked files and worktrees were preserved.

🤖 Generated with Claude Code

Scout and others added 18 commits October 4, 2026 20:48
…mit (gate only; driver still disabled)

PushGate.check re-checks, on the exact commit the harness pushes, that every changed path is inside the trusted
plan's owned paths (git diff-tree -r -z --no-renames --raw; default-deny; independent of patch_policy's matcher, with
a fixture test that both agree). It refuses symlinks, gitlinks, mode and type changes, empty/./.. components,
absolute paths, .git components including git's HFS and NTFS equivalents, .gitmodules, and case-fold or
Unicode-normalization collisions with protected or other changed paths; any gate error or unexpected git output
refuses with a reason code and no push. Owned paths reach the gate only from the coordinator's --owned-path through
the trusted plan and frozen attempt outside every mount; the six-hop precondition trace (decision record) found no
agent-influenced input, so addition 1 of the 2026-10-04 proposal is adopted; addition 2 (plan-time pre-refusal) is
not, because the read derivation is monitoring only. Fail-first evidence: one negative control per refusal class,
red on main and green after, plus the owned-only positive. The public entry point still exits 3 in this commit.
Sources: git v2.43.0 verify_path/fsck .git HFS/NTFS rules; GitHub push-ruleset file-path restrictions (unavailable
to a User-owned repository); OpenHands resolver at the stack pin (no path allowlist).
GPT-6.1 Sol build resolver-gate through the packaged SDK worker; the coordinator commits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…est of the owned-path gate

Replaces the unconditional exit 3 with a start-up self-test: the driver refuses (exit 3, same stderr form) if the
gate module fails to import or a fixture-based negative control (a temporary git repository whose commit changes an
unowned path) is not refused by the gate; otherwise it proceeds as #489's handlers do. RESOLVER.md, the resolver
skill and the command-line tests follow. Kept as its own commit so the gate can be reviewed alone first.
GPT-6.1 Sol build resolver-gate through the packaged SDK worker; the coordinator commits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…first evidence (hot-file protocol: last commit)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s aliases, compiled modules, enforcing files, rule partition, diff_failed)

Gate:
- The .gitmodules NTFS fallback check ports git's is_ntfs_dot_generic loop faithfully (git
  v2.43.0 path.c), including index zero and the empty-prefix alias `~1000000`. Regression tests
  carry all 50 positive and 18 negative .gitmodules vectors from git's t0060-path-utils.sh and
  the .git vectors from t1014.
- New refusal compiled_module_artifact: any __pycache__ component, and the suffixes .pyc, .pyo,
  .so, .pyd, .dylib and .dll, including case, Unicode and NTFS-equivalent spellings. It covers
  additions, modifications and deletions, even inside owned paths, so a compiled module cannot
  shadow a protected or CI-imported .py module.
- resolver.py joins ENFORCING_FILES, because it freezes the owned paths and holds the startup
  self-test. A locally modified driver in the trusted checkout is now refused.
- An explicit UNNAMED_PATH_RULES partition: these refusal classes keep codes and counts without
  publishing path names. The keep-in-step test derives the emitted rules from their producers.
- diff-tree runs with check=False, and a nonzero return refuses as diff_failed (still fail
  closed).

Docs: the gate_reads docstring and the 489-r7 statements are made current or historical. The
residual that enablement makes reachable is recorded prominently: CI files reached only
through advisory or untraced execution forms inside owned paths. The coordinator's
precondition is never to own such a path, and addition 2 stays unadopted with its trigger
named.

Every gate fix was red before it. The coordinator re-ran the resolver suites outside the
builder's sandbox: push_gate and resolver OK (3 skipped), and test_runtime_worker_openhands
and test_openhands_lock_binding OK.

Built by GPT-6.1 Sol through the packaged SDK worker (round resolver-gate-r2); committed by the
coordinator.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…fore the final hot-file commit

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… (hot-file protocol: every hot-file edit in the last commit)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…les refused; honest per-item dispositions; agent-facing refusal lists

- The gate now refuses .gitattributes, .gitignore, .mailmap and the other git-semantic dot
  files at any depth, even inside owned paths: a .gitattributes can change how protected files
  check out (gitattributes(5) at the pinned git). It also refuses agent instruction files
  (AGENTS.md, CLAUDE.md, GEMINI.md and those the agent docs name) at any depth. Each refusal
  has a code and tests that were red before the change.
- The 2026-10-05 entry in RESOLVER.md and the decision record gives every review item one of
  three dispositions, each with its reason: repaired, newly refused, or residual. The
  stdlib-shadowing module next to a CI script stays a recorded residual, with the coordinator
  precondition and its reopen trigger.
- SKILL.md's "What the push gate refuses" and the generated agent instruction name
  compiled_module_artifact and this round's new classes. The source-derived protocol test now
  covers unnamed classes too, so that drift fails.

The coordinator re-ran the suites outside the builder's sandbox, with a writable gitleaks lock:
push_gate, resolver, runtime_worker_openhands and openhands_lock_binding OK, and validate exit 0.

Built by GPT-6.1 Sol through the packaged SDK worker (round resolver-gate-r3); committed by the
coordinator.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…fore the final hot-file commit

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… (hot-file protocol: every hot-file edit in the last commit)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d the token-lanes carrier blocks as instruction files

- push_gate.py INSTRUCTION_FILE_NAMES adds:
  - codex.agents.template.md, the template that tools/adoption/apply_codex_lane.py:111 names;
  - the six token-lanes carrier block basenames, which adoption/hooks/claude/token-lanes-subagent-start.py:16-23,33-38 selects (at 656f263).
- The independent instruction_file refusal now covers them. A regression adds the real template and each carrier block. On the unchanged gate it failed: 21 forbidden commits passed. It passes now.
- The decision record relabels the retained protected-alias case and Unicode coverage as already_covered. "repaired" now names only an actual change.
- RESOLVER.md, the resolver SKILL.md, resolver.py and the fail-first evidence describe the widened inventory.

Built by GPT-6.1 Sol through the packaged SDK worker (round resolver-gate-r4); committed by the coordinator. The coordinator re-ran the three resolver suites with a writable gitleaks lock: 378 tests, OK (3 skips). The builder's validate, evidence_manifest --check and git diff --check exited 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…fore the final hot-file commit

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… (hot-file protocol: every hot-file edit in the last commit)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…iles; set-equality checks for the agent-facing lists

- push_gate.py refuses the adoption/agents component prefix as an instruction_file, at a component boundary. It applies the same alias normalization as the other protected paths: casefold, NFC/NFKC, the HFS form, and NTFS trailing-dot, trailing-space and ADS forms. The installers copy these files into ~/.claude/agents and $CODEX_HOME/agents as subagent system prompts and role carriers, verified at 0e2610d: tools/adoption/install_claude_profile.py and tools/adoption/apply_codex_lane.py.
- Twelve native add/modify/delete controls cover a Claude definition, a Codex role, a new Markdown definition and SHA256SUMS. Eight more cover aliases, nesting and the exact prefix. All failed against the unchanged r4 gate and pass now.
- The doc-side regression parses SKILL.md's backticked instruction list and the generated instruction's comma-separated list, then asserts set equality with INSTRUCTION_FILE_NAMES plus INSTRUCTION_PATH_PREFIXES. The old substring check stayed green with AGENTS.template.md removed from SKILL.md (exit 0). The new one fails that case and an extra or omitted name (exit 1 each), and passes on the complete lists.
- RESOLVER.md, the resolver SKILL.md, resolver.py, the fail-first evidence and the decision record describe the widened inventory.

Built by GPT-6.1 Sol through the packaged SDK worker (round resolver-gate-r5); committed by the coordinator. The coordinator re-ran the three resolver suites with a writable gitleaks lock: 381 tests, OK (3 skips), exit 0. The builder's validate, evidence_manifest --check and git diff --check exited 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…fore the final hot-file commit

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… (hot-file protocol: every hot-file edit in the last commit)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…f the three resolver suites

The r5 Claude Opus read (ACCEPT, 0 P1, 0 P2) noted that the receipt still showed the builder's read-only-lock exit 1 and asked for the coordinator's rerun. This had been open for three rounds. Section 11 now quotes the returned lines:
- With TMPDIR outside the home directory: exit 0, Ran 381 tests, OK (skipped=1). The only skip is the PyYAML parity check. The installed-gitleaks control and both host-path subcontrols ran and passed.
- With the home-relative TMPDIR: exit 0, OK (skipped=3).

Coordinator: validate, evidence_manifest --check and git diff --check exit 0 after registration.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…fore the final hot-file commit

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… (hot-file protocol: every hot-file edit in the last commit)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@seathatflowsinourveins seathatflowsinourveins added the lane:foundation Foundation lane: Claude/Codex setup, hosts, memory, RAG, research, workers label Oct 5, 2026
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-05T09:33:21.699102Z 87f27ea PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@seathatflowsinourveins

Copy link
Copy Markdown
Owner Author

Claude session native-agent-stack-5f: landing at head 87f27ea18f4bc2b62e6bd46680b80ddaf4dc1eee. The command center (wsl-architecture-design) gave its ACK at this exact head; under the user's 2026-10-04 decision it owns merges that touch the hot registry. The read-only cross-family review (GPT-6.1 Sol max, the packaged Codex SDK worker) returned ACCEPT at ee78a711a4c2; this head is reached from it through recorded carry edges (equal owned patch-ids, or a cross-family delta read returning ACCEPT at the edge's target), with no P1 item.

Observed main a11dc5ff33e6fe41cc3684eb8571b408c232c164. coordination/merge_tree_landing_check.py (sha256 a11649fcf2e5844c…) exit 0:

main a11dc5ff33e6fe41cc3684eb8571b408c232c164 head 87f27ea18f4bc2b62e6bd46680b80ddaf4dc1eee base 3b8f9c8a1b938358d65bf422f61592dc3b89407d merged-tree 37444bef8e83af341ac3b941b3b79dd8db238613 merge-tree-exit 0
ok   1: clean three-way merge
ok   2: merged-vs-main paths 12, outside PR-owned 0 []
ok   3: main drift 310 paths, overlap with PR-owned inputs (registry excluded) []
ok   4: registry foreign rows equal True, order preserved True, PR-owned rows 11, unowned top-level keys differing []
ok   5: merged files[] sorted by path with no duplicates: True (10139 rows)
LANDABLE
condition 5: merged files[] sorted and unique (tree 37444bef8e83af341ac3b941b3b79dd8db238613)

Required checks at this head: 7 pass . Unresolved review threads: 0.

@seathatflowsinourveins
seathatflowsinourveins merged commit 4fd7106 into main Oct 5, 2026
24 of 25 checks passed
@seathatflowsinourveins
seathatflowsinourveins deleted the foundation/resolver-owned-path-gate-20261004 branch October 5, 2026 10:00
@seathatflowsinourveins

Copy link
Copy Markdown
Owner Author

Claude session native-agent-stack-5f: post-merge observation. Landed as 4fd710698f53c3581cb1cfe9258ef7b1854ac080 (parent a11dc5ff33e6fe41cc3684eb8571b408c232c164), tree 37444bef8e83af341ac3b941b3b79dd8db238613. The landed tree equals the checked merged tree and the parent is the observed main.

seathatflowsinourveins pushed a commit that referenced this pull request Oct 5, 2026
…ture paths through git plumbing (macOS full suite red since #732)

On macOS, the full suite failed test_case_and_unicode_collisions (controls case_collision and unicode_nfc_collision:
'pass' != 'fail') on every main commit since 4fd7106. GateFixture.agent_commit writes paths through the
filesystem. macOS APFS is case- and normalization-insensitive, so docs/Readme and docs/readme, and the NFC and NFD
forms of docs/café, collapsed into one path. The fixture commit then held no collision, and the gate correctly passed.
The gate is unchanged.

GateFixture.agent_index_commit builds the commit with hash-object, update-index, write-tree, commit-tree and
update-ref, with core.precomposeunicode=false and core.ignorecase=false on every git call. This follows git/git
v2.43.0 t/t2107-update-index-basic.sh:59-69. The tree carries every requested name byte for byte on any filesystem.
- The collision test asserts, through ls-tree, that the tree holds both names' exact UTF-8 bytes before the gate
  assertions run, so a fixture regression now fails with a clear message.
- test_unchanged_protected_file_case_and_unicode_aliases_refuse uses the same helper, so its aliases coexist with
  the unchanged protected file.

Checks:
- tests.test_runtime_worker_openhands_push_gate: 122 run, 121 passed, 1 existing skip (PyYAML absent).
- A Linux independence probe: with a same-named file already in the working tree and both folding settings stored
  as true, the helper kept both exact names and distinct blobs in ls-tree. Restoring the old fixture made the new
  assertions report the lost path.
- validate.py and evidence_manifest --check exit 0.

Built by GPT-6.1 Sol through the packaged SDK worker (round resolver-macos-r1); committed by the coordinator.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
seathatflowsinourveins added a commit that referenced this pull request Oct 5, 2026
…g (macOS full suite red since #732) (#746)

### Scope

- **What this PR changes:** the resolver push-gate tests build their case- and normalization-colliding fixture commits through git plumbing, so the colliding names exist in the tree on any filesystem.
  - Main's macOS full suite has failed `test_case_and_unicode_collisions` (case_collision, unicode_nfc_collision) on every commit since #732 (4fd7106). That failure also shows on every PR.
  - The cause: the fixture wrote the paths through the filesystem, and macOS APFS is case- and normalization-insensitive. Each pair collapsed into one path, so the commit held no collision and the gate correctly passed.
  - The gate (`push_gate.py`) is unchanged.
- **Base commit:** `cb339488e3e0` (origin/main).
- **Lane:** `lane:foundation`, the resolver lane. Tests only, plus the registry row in the last commit.

### SOTA sources

- git/git v2.43.0 `t/t2107-update-index-basic.sh:59-69`: index entries through `update-index --add --cacheinfo`, with no working-tree file. Also git's documentation of `hash-object -w --stdin`, `write-tree`, `commit-tree` and `update-ref`.
- git's `core.ignorecase` and `core.precomposeunicode` settings, both on by default on macOS. They are set to false on every fixture git call.

### Evidence-class table

| Claim | Evidence class | Command / receipt |
| --- | --- | --- |
| main's macOS full suite fails only this test on the main side | hosted CI (observed) | adoption-bootstrap macOS runs 37321600538 (#726) and 37320629278 (#713): the same two subtests; main runs at 4fd7106, c148e04 and cb33948 failed |
| the helper keeps both exact names regardless of the working tree | local_integration (Linux probe) | both names and distinct blobs in `ls-tree` with a pre-existing same-named working-tree file and both folding settings true; restoring the old fixture makes the new tree assertion fail |
| the suite passes | local_integration | `python3 -m unittest tests.test_runtime_worker_openhands_push_gate`: 122 run, 121 OK, 1 existing skip (PyYAML absent) |
| macOS confirmation | pending | this PR's own macOS run (advisory, by the user's 2026-10-05 decision) |

### Local commands run

```
$ python3 -m unittest tests.test_runtime_worker_openhands_push_gate   OK (skipped=1)
$ python3 scripts/validate.py                                         exit 0
$ python3 scripts/evidence_manifest.py --check                        exit 0
$ git diff --check                                                    exit 0
```

### Review

- Built by GPT-6.1 Sol through the packaged SDK worker (round resolver-macos-r1) and committed by the coordinator (Claude session 5f).
- A Claude Opus cross-family read follows.

### Checklist

- [x] No GitHub Actions or workflow changes.
- [x] No secrets printed, logged or committed.
- [x] The gate's behavior is unchanged; tests only.

### Recorded residuals (bounded review, 2026-10-05)

Claude Opus 5.5 cross-family read (session 5f) at ffd5cd3 at `ffd5cd3e99c3` returned ACCEPT with no p1. Under the bounded review rule these p2 items land as recorded residuals, each owed as a follow-up:

- `tests/test_runtime_worker_openhands_push_gate.py:1589 and :1606`: Non-blocking hardening, not a defect.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lane:foundation Foundation lane: Claude/Codex setup, hosts, memory, RAG, research, workers

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant