Repository navigation
OpenHands resolver: owned-path gate on the exact pushed commit, instruction files and aliases refused, driver behind a start-up self-test - #732
Conversation
…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>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
Claude session native-agent-stack-5f: landing at head Observed main Required checks at this head: 7 pass . Unresolved review threads: 0. |
|
Claude session native-agent-stack-5f: post-merge observation. Landed as |
…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>
…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)
Scope
3b8f9c8a1b938358d65bf422f61592dc3b89407dlane:foundationSOTA 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:
The gate uses native
git diff-tree --no-renameson 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
No live resolver, GitHub, model or provider call is part of this evidence.
Local commands run
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:
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)
--no-renamesturns 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
🤖 Generated with Claude Code