Skip to content

Runtime-worker skills program: project-scoped installs and a 137-skill manifest - #429

Merged
seathatflowsinourveins merged 10 commits into
mainfrom
claude/runtime-worker-skills-20260927
Sep 28, 2026
Merged

seathatflowsinourveins merged 10 commits into
mainfrom
claude/runtime-worker-skills-20260927

Conversation

@seathatflowsinourveins

@seathatflowsinourveins seathatflowsinourveins commented Sep 27, 2026 •

Copy link
Copy Markdown
Collaborator

Scope

  • What this PR changes: a skills program for runtime workers.
    • tools/adoption/install_skills.py gains project-scoped installs (--project-dir, --agent, --only) and --check-only.
    • Project installs refuse to run in these cases, before any skills or gh call:
      • the project skills directory is a symlink, or resolves outside --project-dir;
      • a pinned tree does not match the manifest's tree_sha;
      • the manifest's "scope": "project" is used without --project-dir.
    • After an add, a mismatch rolls back with the native CLI. Pruned entries are never installed, and --only <pruned> reports "pruned".
    • Global behaviour is unchanged and keeps install_skills.py: scope the failed-hash rollback to the installed agents (#405) #407's scoped -a claude-code codex rollback.
  • The runtime-worker manifest pins 137 skills from 12 sources:
  • Base: main c3f15cd2.
  • Lane: lane:foundation.
  • Owned paths touched:
    • blueprints/runtime-workers/skills/*
    • tools/adoption/install_skills.py, tools/adoption/runtime_skill_freshness.py
    • tests/test_install_skills.py, tests/test_runtime_worker_skills.py, tests/test_workflow_security_coverage.py
    • .github/workflows/runtime-worker-skills-freshness.yml
    • tools/sota-convergence/blind_checkout.py (classification of repair dispositions)
    • nine anti-pattern rows in docs/harness-defaults.md
    • hash re-registration in manifests/evidence.json, taken from main and re-registered per docs/lanes.md

SOTA sources

  • Installer semantics: vercel-labs/skills v1.7.0 @ 7407f389.

    • src/add.ts L1775-1810: canonical folder vs copy mode.
    • src/installer.ts L128-131, L151-158, L193-200 and L388: agent directories and canonical placement.
    • src/remove.ts L207-215 and L293-331: project alias removal, and the shared data a targeted remove keeps.
    • src/agents.ts L10, L158, L224-232 and L815: agent target paths and Codex detection ($CODEX_HOME, else ~/.codex, or /etc/codex).
    • src/skill-lock.ts: skillFolderHash is the source tree hash.
    • The npm tarball is skills@1.7.0, with the sha512 integrity recorded in the manifest.
  • OpenHands loader (software-agent-sdk @ fcc102a6):

    • openhands-sdk/openhands/sdk/skills/skill.py: folder and SKILL.md frontmatter loading, its own installed-skill state, third-party instruction files at L346-352 and L1091-1121, and the renderer at L1524.
    • context/agent_context.py L93-135 and L171: project-loading flags and disabled_skills.
    • invoke_skill rejects disable-model-invocation skills (da28c773 invoke_skill.py L109).
  • Skill sources: each is pinned to a commit resolved with gh api repos/<owner>/<repo>/commits/<sha>, and each skill carries its own repo@commit path:line merit citation in the manifest.

    Source Commit Selected
    OpenHands/extensions (v0.25.0) bea7a20c 77
    obra/superpowers 8ca22dba 14
    mattpocock/skills c55ee460 12
    anthropics/skills 33375500 9
    trailofbits/skills 0cc1c73a 9
    openai/skills 49f948fa 6
    vercel-labs/agent-skills 063bee94 4
    affaan-m/ECC 2b6e8397 2
    assafelovic/gpt-researcher 0957c301 1
    typesafe-ai/skills 65a39f39 1
    vercel-labs/agent-browser d01253d9 1
    vercel-labs/skills 7407f389 1

Evidence-class table

Claim Evidence class Command / receipt
Project install, check-only, containment and tree refusals, scope enforcement, pruned handling and scoped rollback behave as specified synthetic (fake skills and gh CLI subprocess fixtures) python3 -m unittest tests.test_install_skills tests.test_runtime_worker_skills; the fail-first records for rounds r1-r4 are in blueprints/runtime-workers/skills/validation.json
Every main adoption skill is reused or explicitly excluded; gates are inherited, and drift is refused synthetic plus in-memory negative controls the contract tests; putting verification-before-completion back into main's skills in memory returns exactly ['verification-before-completion: neither reused nor excluded']
The freshness workflow is least-privilege and SHA-pinned integration test tests.test_workflow_security_coverage analyses its contents; floating the checkout pin to @v7 turns it red
Each selected skill exists at its pinned commit and path source_review manifest repo@commit path:line citations; gh api tree listings
Skills installed into worker workspaces, discovered, invoked, and costing a measured amount of prompt not claimed next step: native install into worker workspaces, then invoke-rate measurement and pruning

Local commands run (on this head, rebased onto c3f15cd2)

$ python3 -m unittest tests.test_runtime_worker_skills tests.test_install_skills tests.test_workflow_security_coverage tests.test_workflow_hardening
OK (skipped=2)
$ python3 scripts/validate.py
{"components": 69, "hashed_files": 7927, "profiles": 4, "receipts": 164, "status": "passed"}
$ python3 scripts/host_receipts.py validate
{"receipts": 182, "status": "passed"}

The builder's r4 acceptance on 46176c29 (rebased onto c0966da2) also passed: the three PR modules (79 tests), test_blind_checkout (40), test_pre_push_gate (16), and four docs readers.

Review

  • Round 1: an independent Claude review, repaired by GPT-6 (aa0d1ee7).
  • Round 2 (8efdc35d), two independent reviews; verdicts are in the comment above:
    • GPT-6 cross-family (Codex CLI 0.157.1, gpt-6-astra, max, read-only): changes-needed, with one medium finding (C1: a symlinked project skills directory could reach global state).
    • Headless Claude session: changes-needed, with one medium finding (D1: the pinned-tree check ran only after add).
    • Neither found a blocker or high.
  • Repair r3 (8961e1e1): fixes every finding.
    • Code: C1 and D1, D3 scope marker, D6 reuse by name, D7 pinned CLI version, C5 workflow content assertions.
    • Docs: C6 loader and native controls, D2 third-party instruction files and git-root search, D4 rollback retention, D5 unpublished review relabelled.
    • C2-C4, C7 and C1-manifest-pins were confirmations.
  • Repair r4 (f231d274): adapts to main's Skills trial: remove verification-before-completion under the conflict rule (M4), #381 Amendment 3 #464 by excluding verification-before-completion. It was checked by red→green and a re-admission control, but not independently reviewed.
  • CI: one validate-macos run on r3 failed in tests.test_adoption_status (a SIGHUP subtest timing out). That test is unrelated to this PR, and the job passed on rerun.

Residuals

  • No native skills CLI install and no worker end-to-end run; all installer evidence uses fake executables.
  • New gap: the github-ci-fix / extraction-caller cell is empty, because its only selection was verification-before-completion.
  • Claude Code workers must apply claude_listing through their own skillOverrides; the installer writes no Claude settings.
  • One pinned-tree mismatch fails the whole project run before any add, including --check-only --json.
  • Dated artifacts are left as history: freshness-20260927.json, the r1 discovery line and pins_verified (138).
  • Record fix: an r3 record wrongly listed GPT-6's confirmations as "not relayed". r4 relabels them as confirmed. The cause was the coordinator relaying only non-confirm findings.

Status

Ready to merge after CI on this head. Nothing is installed on any host yet. #425 (OpenHands runtime worker) builds on this.

🤖 Generated with Claude Code

@seathatflowsinourveins seathatflowsinourveins added the lane:foundation Foundation lane: Claude/Codex setup, hosts, memory, RAG, research, workers label Sep 27, 2026
@seathatflowsinourveins
seathatflowsinourveins force-pushed the claude/runtime-worker-skills-20260927 branch from f2037dc to c6c114f Compare September 27, 2026 20:49
seathatflowsinourveins pushed a commit that referenced this pull request Sep 27, 2026
…ef's tip

On 2026-09-27 six PRs (#416, #425-#429) failed CI's validate job on three
registry checks that run in about a second locally:
- the OSV lockfile inventory;
- the blind-checkout label classification;
- the new-workflow security coverage.

scripts/git-hooks/pre-push runs those three test methods against the tip
commit of each pushed ref, in a detached worktree of exactly that commit, and
refuses the push unless all three run and pass. Like CI, which tests one
commit per pull request, it does not test the commits between a ref's old and
new tip. A skip or an expected failure counts as a failure, so the hook
refuses while zizmor is missing and there is a commit to test.
`git push --no-verify` is the visible override. The same core.hooksPath that
enables the pre-commit gitleaks gate activates it.

Scratch and cleanup:
- The scratch base defaults to /var/tmp. The checkout is about 140 MB, and
  file-hierarchy(7) keeps /tmp, usually a tmpfs, for small files.
- The inside-a-repository guard asks git's own discovery, after dropping the
  GIT_DIR that git exports to hooks. An empty .git mount point, as a sandbox
  leaves in /tmp on the WSL host, is not a repository, so it is not refused.
- HUP, INT, QUIT, PIPE and TERM exit through the EXIT trap, as Autoconf's
  configure traps 1, 2, 13 and 15. A reader that closes early
  (`git push 2>&1 | head -n 1`) no longer leaves a worktree behind.
- The runner puts the checked-out tree first on sys.path, so PYTHONSAFEPATH
  plus a PYTHONPATH naming another checkout cannot supply the tests.
- stdin is parsed before the zizmor check, so a deletion needs no zizmor.

Sources:
- githooks(5) for git 2.43.0: pre-push, and the exported GIT_DIR.
- git's templates/hooks--pre-push.sample: the all-zero oid and the read loop.
- git-rev-parse(1) --absolute-git-dir, and git(1) GIT_DIR and
  GIT_CEILING_DIRECTORIES.
- file-hierarchy(7).
- Autoconf lib/autoconf/general.m4 at 0c777e326bd9: traps 1, 2, 13 and 15.
- python3 -P and PYTHONSAFEPATH.
- The repository's pre-commit gate and its test, as the pattern.

tests/test_pre_push_gate.py has 16 tests, run on a shared clone with
plumbing-built commits. Each run sets GIT_CEILING_DIRECTORIES above its
temporary root, and one test pushes natively from a linked worktree to a
shared bare clone. Each test checks that no worktree or scratch is left behind.
Of twenty named mutations of the hook, the tests catch sixteen; the PR lists
the four defensive lines they miss.

Docs: docs/secret-storage.md step 5c (behaviour, prerequisites, override and
cleanup), docs/harness-defaults.md (an anti-pattern row), and the builder
row's Use cell in examples/claude-native/workflows/README.md.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
seathatflowsinourveins added a commit that referenced this pull request Sep 27, 2026
…ef's tip (#438)

On 2026-09-27 six PRs (#416, #425-#429) failed CI's validate job on three
registry checks that run in about a second locally:
- the OSV lockfile inventory;
- the blind-checkout label classification;
- the new-workflow security coverage.

scripts/git-hooks/pre-push runs those three test methods against the tip
commit of each pushed ref, in a detached worktree of exactly that commit, and
refuses the push unless all three run and pass. Like CI, which tests one
commit per pull request, it does not test the commits between a ref's old and
new tip. A skip or an expected failure counts as a failure, so the hook
refuses while zizmor is missing and there is a commit to test.
`git push --no-verify` is the visible override. The same core.hooksPath that
enables the pre-commit gitleaks gate activates it.

Scratch and cleanup:
- The scratch base defaults to /var/tmp. The checkout is about 140 MB, and
  file-hierarchy(7) keeps /tmp, usually a tmpfs, for small files.
- The inside-a-repository guard asks git's own discovery, after dropping the
  GIT_DIR that git exports to hooks. An empty .git mount point, as a sandbox
  leaves in /tmp on the WSL host, is not a repository, so it is not refused.
- HUP, INT, QUIT, PIPE and TERM exit through the EXIT trap, as Autoconf's
  configure traps 1, 2, 13 and 15. A reader that closes early
  (`git push 2>&1 | head -n 1`) no longer leaves a worktree behind.
- The runner puts the checked-out tree first on sys.path, so PYTHONSAFEPATH
  plus a PYTHONPATH naming another checkout cannot supply the tests.
- stdin is parsed before the zizmor check, so a deletion needs no zizmor.

Sources:
- githooks(5) for git 2.43.0: pre-push, and the exported GIT_DIR.
- git's templates/hooks--pre-push.sample: the all-zero oid and the read loop.
- git-rev-parse(1) --absolute-git-dir, and git(1) GIT_DIR and
  GIT_CEILING_DIRECTORIES.
- file-hierarchy(7).
- Autoconf lib/autoconf/general.m4 at 0c777e326bd9: traps 1, 2, 13 and 15.
- python3 -P and PYTHONSAFEPATH.
- The repository's pre-commit gate and its test, as the pattern.

tests/test_pre_push_gate.py has 16 tests, run on a shared clone with
plumbing-built commits. Each run sets GIT_CEILING_DIRECTORIES above its
temporary root, and one test pushes natively from a linked worktree to a
shared bare clone. Each test checks that no worktree or scratch is left behind.
Of twenty named mutations of the hook, the tests catch sixteen; the PR lists
the four defensive lines they miss.

Docs: docs/secret-storage.md step 5c (behaviour, prerequisites, override and
cleanup), docs/harness-defaults.md (an anti-pattern row), and the builder
row's Use cell in examples/claude-native/workflows/README.md.

Co-authored-by: Scout <scout@local>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@seathatflowsinourveins
seathatflowsinourveins force-pushed the claude/runtime-worker-skills-20260927 branch from e72371e to 534af74 Compare September 28, 2026 15:42
seathatflowsinourveins pushed a commit that referenced this pull request Sep 28, 2026
… take reused gates from main

Coordinator direction on the #429 blocker (option b): runtime workers must
not bypass a gate set on main.

- Contract: every skill in adoption/skills/manifest.json is either reused
  by reuse_ref (pins equal to main's, gates not restated) or listed in
  `excluded` with an adoption_ref, a reason and an overturn condition.
  security-audit (#448, 8315274: Codex off, name-only pending the M5c
  bake-off against /security-review) is excluded, and a test fails as soon
  as main's gate on it changes. A synthetic future main skill in neither
  set fails the contract (negative control).
- Gates: install_skills.py resolves each reuse_ref when it reads a
  manifest, refuses an entry whose pin drifted or that restates
  codex_enabled/claude_listing, and takes both gates from main.
  --print-codex-config on the runtime manifest now disables the 16 reused
  skills main keeps off for Codex; before, it printed none. The README
  states that claude_listing is a Claude Code skillOverrides setting that
  the OpenHands SDK does not read (OpenHands/software-agent-sdk@fcc102a6
  skill.py:1123-1130, 182-185, 278-284, 528-538; same blob at da28c773).
- --only naming a pruned project skill fails as pruned, not unknown.
- The README documents that --check-only needs the pinned skills binary,
  and gh sign-in and network in project mode.
- Counts: 138 selected skills from 12 sources (unchanged); 28 of main's 29
  adoption skills reused; 8 exclusions (7 OpenHands, 1 adoption).
- validation.json records the round (fail-first: 9 tests, failures=7,
  errors=2; after the fix: 9 OK) and refreshes every input hash; the
  anti-pattern log gains the dropped-gates row; the 8 changed files are
  re-registered in manifests/evidence.json.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
seathatflowsinourveins pushed a commit that referenced this pull request Sep 28, 2026
… take reused gates from main

Coordinator direction on the #429 blocker (option b): runtime workers must
not bypass a gate set on main.

- Contract: every skill in adoption/skills/manifest.json is either reused
  by reuse_ref (pins equal to main's, gates not restated) or listed in
  `excluded` with an adoption_ref, a reason and an overturn condition.
  security-audit (#448, 8315274: Codex off, name-only pending the M5c
  bake-off against /security-review) is excluded, and a test fails as soon
  as main's gate on it changes. A synthetic future main skill in neither
  set fails the contract (negative control).
- Gates: install_skills.py resolves each reuse_ref when it reads a
  manifest, refuses an entry whose pin drifted or that restates
  codex_enabled/claude_listing, and takes both gates from main.
  --print-codex-config on the runtime manifest now disables the 16 reused
  skills main keeps off for Codex; before, it printed none. The README
  states that claude_listing is a Claude Code skillOverrides setting that
  the OpenHands SDK does not read (OpenHands/software-agent-sdk@fcc102a6
  skill.py:1123-1130, 182-185, 278-284, 528-538; same blob at da28c773).
- --only naming a pruned project skill fails as pruned, not unknown.
- The README documents that --check-only needs the pinned skills binary,
  and gh sign-in and network in project mode.
- Counts: 138 selected skills from 12 sources (unchanged); 28 of main's 29
  adoption skills reused; 8 exclusions (7 OpenHands, 1 adoption).
- validation.json records the round (fail-first: 9 tests, failures=7,
  errors=2; after the fix: 9 OK) and refreshes every input hash; the
  anti-pattern log gains the dropped-gates row; the 8 changed files are
  re-registered in manifests/evidence.json.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@seathatflowsinourveins
seathatflowsinourveins force-pushed the claude/runtime-worker-skills-20260927 branch from 882fc6f to 8efdc35 Compare September 28, 2026 16:42
@seathatflowsinourveins

Copy link
Copy Markdown
Collaborator Author

Independent reviews of 8efdc35d

GPT-6 cross-family review (Codex CLI codex-cli 0.157.1, model gpt-6-astra, effort max, read-only sandbox, via tools/sota-convergence/landscape-sweep/codex_call.sh). Overall: changes-needed

id verdict severity location reason
C1-project-isolation refute medium tools/adoption/install_skills.py:146 Changes are needed because project scope does not enforce filesystem containment. If /.agents/skills links to ~/.agents/skills, matching global SKILL.md bytes and an absent project lock allow installation without --force. The pinned CLI recreates that global directory; failed verification can then delete it when no remaining agent retains it, leaving the global lock stale. The in-memory probe confirmed the wrapper accepts this path. See upstream installation and removal. Reject targets whose resolved parents escape the project.
C1-manifest-pins confirm info blueprints/runtime-workers/skills/manifest.json:4513 Confirmed 138 distinct skills from 12 sources. All 138 path/tree_sha pairs matched independently retrieved, untruncated upstream Git trees at the recorded revisions. With separate project/global directories, in-memory rollback probes preserved pre-existing global files for all three supported agent targets.
C2-adoption-coverage confirm info tests/test_runtime_worker_skills.py:121 All 29 adoption skills are accounted for: 28 reused and security-audit explicitly excluded. Adding an uncovered adoption skill failed the contract probe. Changing security-audit's gate to either promotion or another listing mode failed its dedicated test.
C3-reuse-gates confirm info tools/adoption/install_skills.py:129 Drifted pins, invalid references, and either restated gate were rejected before installer or network calls. Both gates followed altered adoption values at read time. --print-codex-config produced exactly the expected 16 disabled entries.
C4-pruned-selection confirm info tools/adoption/install_skills.py:456 In project mode, selecting a pruned skill returned exit 1 with 'pruned --only', without invoking the installer. This guarantee is explicitly limited to project mode.
C5-workflow-test-coverage refute low tests/test_workflow_security_coverage.py:64 This addition checks only the workflow filename; the module never analyzes its contents. Replacing an action SHA with a floating tag leaves this module green. Add an _analyze assertion. The workflow itself has contents:read, SHA-pinned actions, disabled credential persistence, and no pull_request trigger; direct offline zizmor returned no findings. Repository-wide CI scanning and separate pin assertions mitigate the coverage gap.
C6-loader-documentation amend low blueprints/runtime-workers/skills/README.md:87 The folder, frontmatter, installed-state, and non-Claude-settings claims hold. However, the promised catalog location field is absent: the pinned renderer deliberately emits only name and description, directing invocation through invoke_skill. A consumer cannot obtain the documented location from that catalog. Cite the actual renderer, rather than its stale class docstring.
C6-native-controls amend low blueprints/runtime-workers/skills/README.md:90 The two-control description is incomplete. The pinned SDK also provides AgentContext.disabled_skills and source-loading flags. Installed-skill state does not disable a separately discovered project-directory copy; the native deny-list can exclude it without modifying pinned files. See AgentContext.
C7-validation-evidence confirm info blueprints/runtime-workers/skills/validation.json:124 All nine recorded input hashes match the head, and retained test counts match current discovery. scripts/validate.py passed; all 18 runtime-manifest tests passed when loaded directly. The requested combined unittest command failed before collection because the sandbox denied temporary files even under /var/tmp, so historical full-suite exit codes were not independently reconfirmed. No native installer was run, and the checkout remains clean.

GPT-6 overall: changes-needed

Headless Claude session review (separate claude -p session, Opus, read-only tools, no Agent/Workflow). Overall: changes-needed

id verdict severity location reason
C1 confirm info blueprints/runtime-workers/skills/manifest.json; tools/adoption/install_skills.py:342-344 Pins hold. The manifest has 138 skills from 12 sources: OpenHands/extensions 77, obra/superpowers 15, 28 reused, plus targeted additions. I re-fetched every pin read-only with gh api. All 12 pinned commits resolve, and all 138 directory tree SHAs, SKILL.md sha256 values and byte counts match. Every frontmatter name equals its manifest name, with no duplicates, and upstream's sanitizeName leaves them unchanged. The CLI derives the --skill filter, the canonical folder and the lock key from the frontmatter name (skills@7407f389 src/skills.ts:339-347; installer.ts:316-317; add.ts:1962-1964). With an explicit ref, tryBlobInstall returns null (blob.ts:567), so the lock's skillPath is <path>/SKILL.md as the wrapper expects. Global copies are safe: upstream remove.ts:258-270 without -g builds only cwd paths. My probe put a global copy under --home, then ran a project install whose tree did not match. The wrapper ran remove shared -y -a universal claude-code in the project and left the global folder, the ~/.claude link and the global lock entry in place.
C2 confirm info tests/test_runtime_worker_skills.py:28-59,147-155 adoption_contract_problems passes on the real manifests: 28 reused, security-audit excluded at #/skills/28 with a reason and an overturn condition. The negative control fails as expected on a new main skill that is in neither set, and on double claims, restated gates and bare exclusions. The security-audit test fails on any change to main's (codex_enabled, claude_listing) = (False, 'name-only'), including a promotion, so it acts as a review trigger as README:96-101 states. Main's adoption manifest is unchanged between 8d8f79c and b9eb62d. Note that exclusion is enforced only by this test. My probe added a reuse_ref to #/skills/28 with matching pins, and install_skills.py accepted it (--print-codex-config exited 0).
C3 confirm info tools/adoption/install_skills.py:104-137,369-376 Each probe on a /var/tmp copy of the runtime manifest exited 1 before any skills-bin call: tree_sha drift ("differs from the adoption pin in ['tree_sha']"), codex_enabled restated with main's own value, claude_listing restated as null, and a reuse_ref moved to another index (all seven pin keys reported). A bad prefix gave 'names no adoption skill'. --print-codex-config emits exactly 16 enabled=false tables, which equals the reused skills main disables. Minor: the installer compares 7 pin keys, while the test compares 10 (it adds skill_md_bytes, description_chars and upstream_disable_model_invocation). A leading-zero pointer ('#/skills/024') is also accepted.
C4 confirm info tools/adoption/install_skills.py:445-461 The claim holds as scoped. In project mode, --only <pruned> exits 1 with "pruned --only name(s), not installed in project mode", not 'unknown', and README:152-153 limits the claim to project mode. The global-mode gap is reported separately in D3.
C5 confirm info .github/workflows/runtime-worker-skills-freshness.yml:3-53 Triggers are schedule and workflow_dispatch only; there is no pull_request or pull_request_target, so it cannot run from forks. permissions: contents: read is set at workflow level with no job override. GH_TOKEN is scoped to the report step, and checkout uses persist-credentials: false. All four actions are pinned by 40-hex SHA, and the same SHAs appear in 10-20 other workflows. No github.event or other untrusted expression is interpolated into run blocks. zizmor 1.30.1 --offline reports no findings. The file is in the workflow-security coverage set, and that test passes with zizmor on PATH.
C6 confirm info blueprints/runtime-workers/skills/README.md:77-92 The cited file is openhands-sdk/openhands/sdk/skills/skill.py. It is the same blob, 134e6405, at fcc102a6 and da28c773. The brief's context/skills/skill.py path returns 404, but the PR itself cites the correct path. The cited lines match: 183-186 describe the listing; 278-284 and 528-538 read disable-model-invocation; 936-973 are the user dirs plus installed skills; 1123-1130 are the project dirs. Nothing in skill.py reads Claude Code settings or skillOverrides, so claude_listing has no effect there. The enumeration is incomplete; see D2.
C7 confirm info blueprints/runtime-workers/skills/validation.json:1-572 All 9 input_sha256 values match the tree. Every recorded repair-r2 command reproduces with TMPDIR under /var/tmp: 67 tests OK, test_blind_checkout 40, test_pre_push_gate 16, test_adoption_docs_consistency 37 (1 skipped), test_ecosystem_manifest 56, test_install_claude_profile 58 (3 skipped), test_token_e2e_preregistration 33, and --print-codex-config 16. scripts/validate.py passed (7734 hashed files). The manifests/evidence.json changes only register this PR's files. freshness-20260927.json covers the same 138 names, refs and trees. The worktree stayed clean.
D1 refute medium tools/adoption/install_skills.py:490-500 (preflight) vs 310-316 (add) The project preflight fetches and caches every pinned tree but never compares a skill's tree_sha before the add. Tree identity is checked only after add, in classify_skill (lines 236-238). My probe used a fake gh returning a tree different from the manifest's tree_sha: the wrapper still ran add ... -y -a universal claude-code, then relied on rollback. Codex is a universal-directory agent (skills@7407f389 agents.ts:224-231; skillsDir '.agents/skills'; detected via $CODEX_HOME, ~/.codex or /etc/codex). Upstream remove.ts:293-310 keeps the canonical folder and lock entry whenever another detected agent's install path exists, and for universal agents that path is the canonical folder (installer.ts:151-158). On any Codex host, which is this stack's normal host and HOME defaults to the real home, a stale tree_sha after a repin therefore leaves unverified SKILL.md bytes in /.agents/skills. OpenHands (skill.py:1123-1130) and Codex both load from there, and every rerun re-adds and fails the same way. This does not trigger with today's pins, which all verify. Fix: refuse before any add when pinned_source_trees(source, ref).get(path) != tree_sha.
D2 amend low blueprints/runtime-workers/skills/README.md:77-92,134,240; blueprints/runtime-workers/skills/research.md:130-133 The README says the OpenHands loader reads skill directories, SKILL.md frontmatter and its installed-skill state; research.md says the same. The loader also reads other files. skill.py:1091-1121 loads third-party instruction files (.cursorrules, AGENTS.md, agent.md, CLAUDE.md, GEMINI.md; map at 346-352) as always-on skills. It searches both the work dir and the Git repo root, which _find_git_repo_root finds by walking parents (981-992), and it loads the project skill dirs at that root too. Project and user loading are opt-in (agent_context.py:93-135, default False), but README:240 tells operators to enable 'the normal native project loader'. The README's example OH_PROJECT="$STACK_ROOT/.runtime/openhands" (line 134) sits inside this repository's Git root. An OpenHands worker there would ingest this repo's AGENTS.md and CLAUDE.md, which also confounds the README's first-prompt measurement. The narrow 'no Claude settings' statement stays true.
D3 amend low tools/adoption/install_skills.py:445-446 Project scope is a convention, not enforced. Without --project-dir, the runtime manifest takes the global path. A dry run with a version-only fake CLI planned 138/138 add ... -g -y -a claude-code codex installs into ~/.agents/skills and ~/.claude/skills. That set includes 110 skills with no Codex or Claude gate. In global mode, pruned entries are also processed: a real fake-CLI run of --only <pruned> installed the skill globally and exited 0. README:109-110 documents that global behaviour is kept, every README command goes through worker_skills with --project-dir, and all 138 entries are currently 'trial', so there is no current effect. A manifest-level scope marker that the installer enforces would close this.
D4 amend low blueprints/runtime-workers/skills/README.md:166-179 The README says a post-add mismatch 'removes that skill with the native CLI' and that the in-use guard 'can retain' it. Per D1's upstream citations, retention is certain for universal and claude-code rollbacks whenever Codex or another universal-directory agent is detected under the wrapper's HOME. The caller's CODEX_HOME passes through even with a test --home. The README should say so, and should say the retained content stays loadable by workers. The retention itself is a deliberate, recorded choice (docs/harness-defaults.md row 'Widening rollback after a remover retains shared data'); only the wording overstates cleanup.
D5 amend low blueprints/runtime-workers/skills/validation.json:118 independent_review.source cites 'skills-review-r1.json' with a sha256, but that file is not in the tree (git ls-files finds 0 matches), so the cited review cannot be checked. Publish a sanitized receipt or relabel it as unpublished.
D6 amend info tools/adoption/install_skills.py:81,104-137 reuse_ref is an index-based JSON pointer, and reused entries repeat main's pins. Any insertion, reorder or repin in adoption/skills/manifest.json makes load_manifest refuse the whole runtime manifest, even for an unrelated --only run. It also fails the contract test on main's PR. This fails closed, but it couples the two lanes; document it in docs/lanes.md or key reuse by name.
D7 amend info tools/adoption/runtime_skill_freshness.py:96-97; .github/workflows/runtime-worker-skills-freshness.yml:12 ok hard-codes CLI 1.7.0, so a deliberate CLI repin fails the weekly job until the code changes. zizmor --persona=pedantic reports only concurrency-limits and an anonymous job definition.

Claude overall: changes-needed: The PR's seven headline claims hold on primary evidence. I re-fetched all 138 pins, and all 67 PR tests plus validate.py and the recorded commands reproduce. Name identity with the pinned skills CLI is clean. Reuse-gate refusal, the coverage contract, the 16 Codex disables, the pruned --only handling in project mode, global-copy-safe rollback and the least-privilege, SHA-pinned workflow all check out. Four things should change before merge. First, D1: compare each pinned tree against the manifest's tree_sha during preflight, before any add, because on Codex hosts the native rollback keeps whatever was added. Second, D4: state in README:166-179 that rollback retention is certain on hosts with Codex or another universal-directory agent. Third, D2: finish the OpenHands read-set in README:77-92 and research.md:130-133 (third-party AGENTS.md/CLAUDE.md files and the Git-root search), and move or caveat the example worker paths inside $STACK_ROOT. Fourth, D5: publish or relabel the uncommitted skills-review-r1.json. D3, D6 and D7 are low or informational follow-ups. None of the findings is blocker or high severity.

Neither review found a blocker or high. All findings go to one repair round (medium: GPT6-C1 project containment, CL-D1 tree check before add). Its dispositions will be recorded in blueprints/runtime-workers/skills/validation.json.

🤖 Generated with Claude Code

seathatflowsinourveins pushed a commit that referenced this pull request Sep 28, 2026
… take reused gates from main

Coordinator direction on the #429 blocker (option b): runtime workers must
not bypass a gate set on main.

- Contract: every skill in adoption/skills/manifest.json is either reused
  by reuse_ref (pins equal to main's, gates not restated) or listed in
  `excluded` with an adoption_ref, a reason and an overturn condition.
  security-audit (#448, 8315274: Codex off, name-only pending the M5c
  bake-off against /security-review) is excluded, and a test fails as soon
  as main's gate on it changes. A synthetic future main skill in neither
  set fails the contract (negative control).
- Gates: install_skills.py resolves each reuse_ref when it reads a
  manifest, refuses an entry whose pin drifted or that restates
  codex_enabled/claude_listing, and takes both gates from main.
  --print-codex-config on the runtime manifest now disables the 16 reused
  skills main keeps off for Codex; before, it printed none. The README
  states that claude_listing is a Claude Code skillOverrides setting that
  the OpenHands SDK does not read (OpenHands/software-agent-sdk@fcc102a6
  skill.py:1123-1130, 182-185, 278-284, 528-538; same blob at da28c773).
- --only naming a pruned project skill fails as pruned, not unknown.
- The README documents that --check-only needs the pinned skills binary,
  and gh sign-in and network in project mode.
- Counts: 138 selected skills from 12 sources (unchanged); 28 of main's 29
  adoption skills reused; 8 exclusions (7 OpenHands, 1 adoption).
- validation.json records the round (fail-first: 9 tests, failures=7,
  errors=2; after the fix: 9 OK) and refreshes every input hash; the
  anti-pattern log gains the dropped-gates row; the 8 changed files are
  re-registered in manifests/evidence.json.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@seathatflowsinourveins
seathatflowsinourveins force-pushed the claude/runtime-worker-skills-20260927 branch from 8efdc35 to e979fc0 Compare September 28, 2026 17:59
seathatflowsinourveins pushed a commit that referenced this pull request Sep 28, 2026
… take reused gates from main

Coordinator direction on the #429 blocker (option b): runtime workers must
not bypass a gate set on main.

- Contract: every skill in adoption/skills/manifest.json is either reused
  by reuse_ref (pins equal to main's, gates not restated) or listed in
  `excluded` with an adoption_ref, a reason and an overturn condition.
  security-audit (#448, 8315274: Codex off, name-only pending the M5c
  bake-off against /security-review) is excluded, and a test fails as soon
  as main's gate on it changes. A synthetic future main skill in neither
  set fails the contract (negative control).
- Gates: install_skills.py resolves each reuse_ref when it reads a
  manifest, refuses an entry whose pin drifted or that restates
  codex_enabled/claude_listing, and takes both gates from main.
  --print-codex-config on the runtime manifest now disables the 16 reused
  skills main keeps off for Codex; before, it printed none. The README
  states that claude_listing is a Claude Code skillOverrides setting that
  the OpenHands SDK does not read (OpenHands/software-agent-sdk@fcc102a6
  skill.py:1123-1130, 182-185, 278-284, 528-538; same blob at da28c773).
- --only naming a pruned project skill fails as pruned, not unknown.
- The README documents that --check-only needs the pinned skills binary,
  and gh sign-in and network in project mode.
- Counts: 138 selected skills from 12 sources (unchanged); 28 of main's 29
  adoption skills reused; 8 exclusions (7 OpenHands, 1 adoption).
- validation.json records the round (fail-first: 9 tests, failures=7,
  errors=2; after the fix: 9 OK) and refreshes every input hash; the
  anti-pattern log gains the dropped-gates row; the 8 changed files are
  re-registered in manifests/evidence.json.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@seathatflowsinourveins
seathatflowsinourveins force-pushed the claude/runtime-worker-skills-20260927 branch from e979fc0 to 46176c2 Compare September 28, 2026 18:58
Scout and others added 10 commits September 28, 2026 15:18
…l pinned manifest

GPT-6 (astra, max) build. install_skills.py gains --project-dir and --agent
targets with the default global behaviour unchanged, project lock identity and
pinned source-tree checks, and scoped rollback. The runtime-worker manifest pins
138 skills (77 OpenHands extensions v0.25.0, all 15 superpowers lifecycle
skills, 28 existing adoption entries by reference) with a report-only freshness
checker and scheduled workflow. Not installed into any worker yet; invocation
and pruning are measured after install.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
GPT-6 (astra, max) repair of all seven findings; the blocker (#407's scoped
global rollback) was already resolved at the rebase and is kept byte for
byte. Project rollback now removes exactly the add targets and checks the
canonical folder, project lock entry and Claude link independently,
reporting a retained copy as an error instead of widening the deletion
(skills@7407f389 src/remove.ts:209-333). A project .claude/skills/<name>
that is not a symlink to the canonical folder is local-modified unless
--force (installer.ts:254-264). Every selected source/ref is fetched before
any add; an unavailable gh is reported as unverified, not as a mismatch.
The pruning rule records Claude Code's skill-listing budget and treats
dropped descriptions as unknown. 52 tests pass; five fail-first runs are
recorded in validation.json.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…alues

Found and fixed through the upstream gh-fix-ci skill (openai/skills@49f948fa)
on the GPT-6 lane, then reviewed by the coordinator:
- tests/test_workflow_security_coverage.py lists
  runtime-worker-skills-freshness.yml; the workflow itself already passes the
  hardening, pinning, permissions and zizmor checks.
- blind_checkout: the review disposition "fixed" joins the closed-vocabulary
  labels (as in #416), and the superseded rollback assertion's disposition is
  data.
- harness-defaults anti-pattern row: run acceptance with TMPDIR outside every
  repository.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…emp dirs

On macOS the temporary directory sits under /var, a symlink to /private/var,
so local.resolve() returned the /private/var form while the expected path kept
the /var form. validate-macos failed 8 subtests of the two new project-install
tests (run 36349458060). Resolve the expected path too, as
tests/adaptive_paper_hermetic.py already does with os.path.realpath.

Reproduced on Linux with TMPDIR set to a symlinked directory: 8 failures
before, OK after; the module passes 38 tests with a plain or symlinked TMPDIR.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… take reused gates from main

Coordinator direction on the #429 blocker (option b): runtime workers must
not bypass a gate set on main.

- Contract: every skill in adoption/skills/manifest.json is either reused
  by reuse_ref (pins equal to main's, gates not restated) or listed in
  `excluded` with an adoption_ref, a reason and an overturn condition.
  security-audit (#448, 8315274: Codex off, name-only pending the M5c
  bake-off against /security-review) is excluded, and a test fails as soon
  as main's gate on it changes. A synthetic future main skill in neither
  set fails the contract (negative control).
- Gates: install_skills.py resolves each reuse_ref when it reads a
  manifest, refuses an entry whose pin drifted or that restates
  codex_enabled/claude_listing, and takes both gates from main.
  --print-codex-config on the runtime manifest now disables the 16 reused
  skills main keeps off for Codex; before, it printed none. The README
  states that claude_listing is a Claude Code skillOverrides setting that
  the OpenHands SDK does not read (OpenHands/software-agent-sdk@fcc102a6
  skill.py:1123-1130, 182-185, 278-284, 528-538; same blob at da28c773).
- --only naming a pruned project skill fails as pruned, not unknown.
- The README documents that --check-only needs the pinned skills binary,
  and gh sign-in and network in project mode.
- Counts: 138 selected skills from 12 sources (unchanged); 28 of main's 29
  adoption skills reused; 8 exclusions (7 OpenHands, 1 adoption).
- validation.json records the round (fail-first: 9 tests, failures=7,
  errors=2; after the fix: 9 OK) and refreshes every input hash; the
  anti-pattern log gains the dropped-gates row; the 8 changed files are
  re-registered in manifests/evidence.json.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…rity-audit overturn (R2-6)

The README said OpenHands withholds a skill from the model only through
SKILL.md disable-model-invocation, and research.md said the SDK reads only
SKILL.md frontmatter. load_user_skills also loads OpenHands' own enabled
installed skills (skill.py:936-973 at fcc102a6). Both now say the loader
reads skill directories, SKILL.md frontmatter and its installed-skill enable
state, never Claude Code settings or a skillOverrides key. The
available_skills citation moves from lines 182-185 to 183-186.

The security-audit overturn now names a promotion as codex_enabled true or
claude_listing on; any other change to main's gate still fails the
exclusion test and needs a new review. The test message says so.

validation.json records R2-6, the corrected sources, the red excerpt in log
order, refreshed input hashes and the rerun checks; the five changed files
are re-registered in manifests/evidence.json.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
research.md and validation.json record the second rebase, onto 8d8f79c
(#460), the merged docs/harness-defaults.md input hash and the checks rerun
on that tree. The manifests/evidence.json re-registration that was part of
this commit was dropped when the branch was rebased onto c1581fa, and is
redone at the tip.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…d, project scope, name-keyed reuse

Two independent reviews of 8efdc35, a GPT-6 cross-family review
(codex_call.sh, gpt-6-astra max) and a headless Claude session review,
returned changes-needed with no blocker or high finding. Each code item
failed first against the unchanged code:

- GPT6-C1: refuse a project whose .agents/skills, .claude/skills or
  skills-lock.json passes through a symlink, whose selected skill paths
  resolve outside it, or that is --home, before any skills or gh call.
- CL-D1: compare every selected pinned tree with its tree_sha in the
  preflight and refuse before any add; the native in-use guard keeps a
  rolled-back copy while a detected agent such as Codex reads it.
- CL-D3: the runtime manifest carries "scope": "project" and is refused
  without --project-dir; pruned entries are never installed in either mode.
- CL-D6: reuse_ref and adoption_ref name the adoption manifest and match
  by skill name.
- CL-D7: runtime_skill_freshness.py reads the CLI version and ref from
  the manifest's cli pin.
- GPT6-C5: offline zizmor analysis of the freshness workflow, with
  floating-tag, persist-credentials and permissions controls.

Docs: the rendered OpenHands skill list omits <location>; disabled_skills
and the loading flags are its controls; third-party instruction files load
from the work directory and Git root, so the example worker projects move
outside the checkout; rollback retention is certain when Codex is detected;
skills-review-r1.json is labelled unpublished.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…on with main (M4, #464)

Main c0966da (#464) moved verification-before-completion from the
adoption skills to its excluded list under the skills-trial conflict
rule M4. On the rebased tree the unchanged coverage contract and
installer refused the runtime manifest's reuse_ref for it (67 tests,
6 failures, 2 errors).

- Exclude the skill in the runtime manifest with its reviewed pin, the
  M4 and AGENTS.md:16 reason, and the overturn "main re-admits it"; it
  carries no adoption_ref because main no longer lists it in skills.
- New test_verification_before_completion_stays_excluded_until_main_readmits_it
  and the revised superpowers lifecycle test failed first; a source-count
  test ties sources and the research table to the selected skills.
- Recount: 137 skills from 12 sources (superpowers 14), 27 reused
  (16 Codex-disabled on main), 9 exclusions, 15 empty cells (new
  github-ci-fix/extraction-caller gap), 1,020,955 bytes and 36,739
  description characters.
- validation.json: repair_r3.not_relayed becomes the five confirmed
  CONFIRM ids; repair_r4 records the rebase, sources, fail-first and checks.
- State Codex detection precisely ($CODEX_HOME, ~/.codex when unset, or
  /etc/codex) in research.md, the harness-defaults row and a code comment.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…dence.json

Main's evidence.json with the 13 PR files registered at their current
sha256 and byte size (8 added, 5 updated); no other entry changed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@seathatflowsinourveins
seathatflowsinourveins force-pushed the claude/runtime-worker-skills-20260927 branch from 46176c2 to a41f144 Compare September 28, 2026 19:18
@seathatflowsinourveins seathatflowsinourveins changed the title Runtime-worker skills program: project-scoped installs and a 138-skill pinned manifest Runtime-worker skills program: project-scoped installs and a 137-skill manifest Sep 28, 2026
@seathatflowsinourveins
seathatflowsinourveins marked this pull request as ready for review September 28, 2026 19:19
@seathatflowsinourveins
seathatflowsinourveins merged commit bdf25d2 into main Sep 28, 2026
29 of 32 checks passed
@seathatflowsinourveins
seathatflowsinourveins deleted the claude/runtime-worker-skills-20260927 branch September 28, 2026 19:38
seathatflowsinourveins added a commit that referenced this pull request Sep 28, 2026
… M4 host removal receipt (#467)

* install_skills: read back a global rollback like #429's project path; M4 host removal receipt

The skills 1.7.0 CLI's scoped `remove <name> -g -y -a claude-code codex` exits 0 yet keeps the
canonical ~/.agents/skills/<name> folder and its lock entry whenever another detected universal
agent resolves to that folder (vercel-labs/skills src/remove.ts at 7407f389; installed
dist/cli.mjs L6883-6907). Codex reads that folder (openai/codex codex-rs/ext/skills/src/host_roots.rs
L103-108, rust-v0.157.1). The global rollback still reported 'rolled-back' without a read-back.

- install_skills.py: the global rollback reads back canonical folder, lock entry and Claude link
  and reports #429's 'error' with its reason strings; a remove that cannot run returns 'error'
  instead of raising. Tests on main's fake-CLI fixtures; failing-first 6 of 52 against bdf25d2.
- Receipt for the M4 host removal on this host (scoped remove kept canonical + lock; set asserted;
  unscoped remove completed; profile install read-back; skills_status 28; override drop).
- Correction addendum to the M4 host step; one anti-pattern row cross-linked with #429's.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* install_skills: read-back resolves CLAUDE_CONFIG_DIR and refuses an unverifiable lock (review repair)

Repair round for the cross-family review of #467 at 6744c2e (codex exec, gpt-6-astra, effort max:
MERGE-AFTER-FIXES) and the independent verifier's coverage note.

- claude_skills_dir: the global Claude link is read back where skills 1.7.0 puts it,
  $CLAUDE_CONFIG_DIR/skills when set and not blank (npm dist/cli.mjs L1398, L1511), else ~/.claude/skills.
- lock_retains: only a missing lock or a parsed lock without the entry confirms removal; a malformed
  or unreadable lock, or any OSError/ValueError during the read-back, is 'error' with
  "rollback could not be verified" in both modes.
- Tests: fake CLI mirrors CLAUDE_CONFIG_DIR, adds lock_after_remove faults and a deleted binary; the
  module strips a host CLAUDE_CONFIG_DIR. 57 OK; 5 fail against 6744c2e's installer.
- Native: m4_claude_config_dir_probe.py (pinned CLI links only under CLAUDE_CONFIG_DIR; a blank value
  falls back; pre-repair installer false 'error' on an unrelated ~/.claude copy, repaired 'rolled-back');
  the unchanged reproduction rerun with the repaired installer matches its first output.
- Receipt data.review_repair_round, a limitation, addendum and anti-pattern row updated; addendum nit
  (skillOverrides, not settings).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* install_skills: resolve CLAUDE_CONFIG_DIR with the CLI's trim() and path.join rules

Final round after the re-check of bb3c88f (codex exec, gpt-6-astra, effort max: F1 partial) and a
second stack-verifier (9/10 confirmed; the same gaps):
- JS_TRIM_CHARS: the 25 code points node's String.prototype.trim removed over every code point
  (str.strip keeps U+FEFF and strips U+001C-U+001F, U+0085); test compares it with node on PATH.
- The global Claude folder is normalized as path.join does (os.path.normpath; a leading // collapses).
- Fake CLI mirrors both; 59 OK; against bb3c88f's installer 3 subtests fail and 1 errors.
- Probe arms for a '..' value and U+FEFF alone, and node's trim set and version; probe and the
  unchanged reproduction rerun with the final installer; receipt block, limitation and addendum
  (line references recomputed: L17-33, L125-136, L449-451, L452-478) updated.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Scout <scout@local>
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
seathatflowsinourveins added a commit that referenced this pull request Oct 4, 2026
…ges 1-2, offline-tested) (#489)

### Scope

This PR adds the host driver for the OpenHands issue-to-PR resolver on top of the merged #425 recipe. One explicitly scoped, owner-authored issue can produce one validated patch, one draft PR, one COMMENT review and one repair round. The model's patch is not executed on the host. A draft PR can execute it in GitHub CI under the resolver-mode amendment decided on 2026-10-04: option 1, with a trusted pre-push gate that checks every agent commit before any push (see "Pre-push gate" below).

- **Base commit:** `d2fc3803e01178b4687771d0bf91faf453bb6933`, merged in `db24156b9` without rebasing or force-pushing. The original PR head `501bcc9e50bf3ffa3acc82b6ecf20aa4e23c593b` remains in history. Head: `7c1d24cc5600bed8b56435aef37ec8d267f889fe`. On top of `0f7b27578` it corrects this round's dates to 2026-10-04, as `date -u` gives them, in `b763cb294` (text only; the commit also resets the registry to main's copy), and re-registers the rows in `7c1d24cc5` (the last commit). On top of `4eb6b4cc9`, `0f7b27578` added the merge `4f963c9b2` of main `6af8e55bd` (#681, CI least privilege, with #672, #626 and #679). It also adds the gate fix that merge needed: the derivation follows code that gate code runs, not code it only reads (`GateReads.executed`), in `57d0dc065`. Main's #679 made `tools/adoption/install_claude_profile.py` read three workflow scripts, and following them had protected all of `blueprints/`. The decision record, `RESOLVER.md` and evidence part 8 are in `85262b1f8`, and the registry rows in `0f7b27578` (the last commit). The main-merge list below predates this merge. On top of `21b24dede`, `4eb6b4cc9` added the repair round for the cross-family read of `40f12ba5` (GPT-6.1 Sol, findings P1 and P2): the merge `db287ea72` of main `3bdacabd5`, the gate-data reader, the instruction fix and their tests in `86688e1e1`, the decision record, `RESOLVER.md` and evidence part 7 in `0092ae213`, the merge `4a03dd796` of main `ba0e8c48f`, the figures on that merge in `1ec9d04c3`, a receipt test and the final control runs in `e1b4f5c68`, and the registry rows in `4eb6b4cc9` (the last commit). Before that, on top of `050bca5d5`: the zizmor pin read from `.github/requirements-ci.txt` in `a5194090b` (registry `4009a96ec` and `40f12ba51`), then the CI-blocker round for CodeQL alert 90 and validate-macos (the merge `d1bb9cf15` of main `f474f6d22`, `36440d64a`, `9602c2274`, `8be0c1d39`, registry `21b24dede`). On top of `456f8fee6`, `050bca5d5` added the decided amendment's trusted pre-push gate: code and tests in `00905292d` and `48831bc65`, the decision record, `RESOLVER.md` and the evidence log in `868f02501`, and the registry rows in `050bca5d5`. Before that, on top of `b0c11c324`, came wording-only fixes for the [independent re-check](#issuecomment-5973091307) and the coordinator's follow-up: content in `f4e7aa2a2` and `54438ce7f`, registry rows in `d89ad3b44` and `456f8fee6`. This description was written for `b0c11c324`. Parts were updated for `050bca5d5`: the "Pre-push gate" bullet, the SOTA "Pre-push gate" line, the evidence rows and commands, the "Decision record" section and the decision item under "What is not done". For `4eb6b4cc9`, this line, the main merges, the owned paths, the "Pre-push gate" bullet and a SOTA "Gate data" line are updated; the evidence table and the commands still describe `050bca5d5`, and the later rounds' evidence is in `evidence/push-gate-fail-first.txt` parts 6 and 7 and the PR comments. The SOTA "Step 6 repair round" line was updated for `456f8fee6`.
- **Main merges during custody.** Each followed the hot-file protocol: main's `manifests/evidence.json`, then this PR's owned rows re-registered.
  - `9b0b8d6d2` in `37cb51899`;
  - `4ced29230`, which carried main's `AGENTS.md` update, in `85830e815`;
  - `59f8a1e36` in `d35633fad`;
  - `d2fc3803e`, which contains `ecea28654`, in `db24156b9`;
  - `f474f6d22`, which carried main's macOS CI scope step (`e0c329ae9`), in `d1bb9cf15`;
  - `3bdacabd5` in `db287ea72`;
  - `ba0e8c48f`, which carried the OSV and SARIF hardening port, in `4a03dd796`.

  Main has since advanced to `b5b9c9ddb` (#672), which is not merged. Its 13 paths are jCodeMunch carrier files, documents, one test and evidence; none is a workflow or a gate script. The coordination merge-tree landing check of `4eb6b4cc9` against it reports LANDABLE: a clean three-way merge, 21 merged-vs-main paths and none outside the PR-owned set, no overlap with main's 13 drifted paths, the registry's foreign rows equal and in order, 20 PR-owned rows, and a sorted `files[]` without duplicates (9823 rows).
- **Lane:** `lane:foundation`.
- **Owned content paths:** `blueprints/runtime-workers/openhands/RESOLVER.md`, `resolver.py`, `resolver/{patch_policy,gh_harness,outgoing_guard,push_gate,gate_reads}.py`, `skills/resolver/SKILL.md`, `host.py`, `dispatch.py`, `worker.py`, `receipt.py`, `e2e/task.py`, both `evidence/stage2-*fail-first.txt` logs and `evidence/push-gate-fail-first.txt`, `docs/decisions/2026-09-28-openhands-resolver-isolation.md`, and the three OpenHands test modules (`tests/test_runtime_worker_openhands_push_gate.py` is new). The registry commits re-register these files over main's `manifests/evidence.json` and regenerate the four foundation reports through their supported commands. The last full pass is in the merge `db24156b9`. `b0c11c324` refreshes the rows of the three files that the step 6 repair round changed. `050bca5d5` refreshes the six rows the gate change touched and adds three new files' rows. `4eb6b4cc9` registers the 20 PR-owned files over main `ba0e8c48f`'s registry: 7 rows updated and 13 added, `resolver/gate_reads.py` among them. Both generators' `--check` passed each time, so no report was rewritten.
- **Existing main defect disclosed.** Commits `0c9b7acf6` and `3e3877979` fix the SWE-bench skill contract and instruction. At the merged base, `host.py:382` requires `verification-before-completion` and `e2e/task.py:52` tells the worker to invoke it, while the runtime manifest lists that skill under `excluded`. The fix keeps the excluded skill out of both contracts and preserves the instruction to run and report the reproducing tests before finishing.
- **Custody repair.** The alias-refusal fixture now builds all seven entries directly in a scratch Git index. It preserves case and decomposed Unicode names on filesystems that fold them, disables Git's macOS argument precomposition for those insertion commands, and exports the cached patch without restaging the worktree. All existing refusal assertions remain, with an added check that every intended name reached the validator.
- **Review-round repair** (`2ede7b871`, tests only; its registry row in `b2d556c95`). The outgoing-guard fixtures no longer depend on `TMPDIR`. Two `host_path` assertions now use a synthetic absolute host root. The symlink case needs a real temporary root, so it probes that prerequisite. It skips with an explicit message when the root matches a `scripts/validate.py` `PRIVATE_CONTENT` pattern, such as a personal home path; the reason was reworded in `cf42c6d08`. No assertion or reason label changed.
- **Step 6 repair round** (`cf42c6d08`: documentation and one test's comment and skip text; its registry rows in `b0c11c324`).
  - The decision record's option 2 and `RESOLVER.md` now state that the resolver pushes to and opens PRs only in this repository. A fork therefore needs a separately reviewed harness change before any run.
  - Both options now address condition 3 and CI egress.
  - The G4 residual gives the actual refusal order.
  - No behaviour changed.
- **Pre-push gate** (the amendment's decision of 2026-10-04; content in `00905292d`, `48831bc65` and `868f02501`, registry in `050bca5d5`; the cross-family repair in `86688e1e1`, `0092ae213`, `1ec9d04c3` and `e1b4f5c68`, registry in `4eb6b4cc9`).
  - **Push path.** `GhHarness.push` runs `resolver/push_gate.py` on the exact agent commit before any push and pushes that commit by name (`<sha>:refs/heads/<branch>`, never `HEAD`). `GhHarness.run` refuses any push of a commit the gate did not pass, and any push without a gate.
  - **Refusals.** The gate diffs the commit against its base. It refuses, with no push, changes to `.github/**`, a `CODEOWNERS` anywhere, the resolver's gate and harness code, the workflow-policy tests (`tests/**.py` naming `.github`), and every file that a workflow reachable from the push or the PR executes or reads as a gate. That last list is derived from the workflow files by a text-level reader that reuses `patch_policy.names_in_text`: the files `run:` steps name, the import closure of the gate scripts, unittest discovery and local actions. It also refuses a step that interpolates untrusted event text.
  - **Gate data** (cross-family P1). The gate also refuses changes to the data those gate scripts read (`ci_read`, `resolver/gate_reads.py`), such as `blueprints/convergence-practice/contract.schema.json`, which `scripts/validate_convergence.py` reads. Each gate script's expressions are evaluated to the repository paths they spell. An exact path protects that file, and a computed one protects the directory or glob it resolves under. Code that gate code runs or reads is followed to a fixpoint. A read the reader cannot resolve refuses every commit (`gate_input_unresolved`) rather than protecting the whole tree; none is unresolved on the merged tree. The cost is breadth. The merged tree `4a03dd796` has 10,528 tracked files, and the gate protects 7,559 of them (441 before). Outside `evidence/`, `tests/` and `.github/` it protects 633 of 3,444 (122 before). Most of it comes from `tools/sota-convergence/gap_wave_ledger.py`'s computed `evidence/artifacts` and `docs/*.md` paths. The decision record's residual risks name the two deferred narrowings, and a repository test bounds the breadth.
  - **zizmor.** The gate runs the zizmor CI pins (1.30.1) with its own flags (no config, no ignores, offline). The excessive-permissions, dangerous-triggers, cache-poisoning, artipacked and template-injection audits refuse, and a missing or wrong zizmor fails closed.
  - **Trusted copy.** The gate runs only from the checkout it sits in, never from inside an agent tree. Its four files (`push_gate.py`, `patch_policy.py`, `gate_reads.py`, `gh_harness.py`) must equal the trusted commit's and the base's blobs, the trusted commit must be on main at the base, and the trusted commit is recorded. Each check leaves one record per commit in `resolver-outcome.json` (`push_gate`) and a code-and-count projection in the receipt.
  - **CLI.** `run` takes `--zizmor`, and the plan reports `push_gate: trusted_copy_checked`.
  - **Agent instructions** (cross-family P2). The resolver skill and the generated instruction name every category the gate refuses. When a fix would need such a change, including a new or changed test (`tests/**` is protected here), they tell the agent to stop and report instead of editing. A test keeps both in step with the gate's rules and the receipt's.

The resolver's Stage 1 logic covers owner/edit provenance, a patch policy derived from the base's host-executed files, fixed `gh` templates, guarded outgoing text and a bounded PR loop. Stage 2 adds:
- the same O1 containment topology;
- fresh P0-P2 receipts and `stage-gates.json`;
- G5 checks before container creation and at dispatch;
- a pinned-main checkout with no MCP servers, and a fixed skill set;
- a validated patch whose committed diff must match byte for byte.

Its receipt does not infer successful completion from model-writable data.

### Independent review history

1. Stage 1 received a read-only GPT cross-family review (`gpt-6-astra`, max). Four findings were repaired, and a Claude closure review confirmed them closed: import shadowing, edited issue provenance, missing required contexts, and a review bound to a different commit.
2. Stage 2 received three independent Claude reviews. The verifier accepted with low notes; the security and design reviewers requested changes. The retained repair round addressed:
   - patch-content checking before `git apply`;
   - binding the G4 reviewer argv to a recorded hash;
   - retaining a PR record when GitHub holds the PR;
   - the residuals comment;
   - the receipt after a dispatch failure;
   - the excluded-skill instruction;
   - containment evidence;
   - fourteen smaller items.

   Both fail-first logs are unchanged: their blob SHAs at this head (`ed55377f`, `3f41c01b`) equal those at `501bcc9e`.
3. Stage 2's requested separate read-only GPT-6.1 Astra/max review through the packaged lane (custody contract step 6(a)) was pending at `b2d556c95`. It ran at `db24156b9` (job `rev-489-astra`) and returned **repair** with one should-fix finding, which `cf42c6d08` repairs (see "Step 6 reviews" and "Step 6 repair round"). The builder's diagnosis and tests do not count as that review.
4. The headless Opus 5.5 closure review of the whole repaired head (step 6(b)) was also pending at `b2d556c95`. It returned **repair** at `db24156b9`, with two should-fix and seven minor findings; "Step 6 repair round" gives each disposition. Before the PR leaves draft, the coordinator must still:
   - have the reviewers re-read this delta (contract step 6);
   - resolve every review thread;
   - confirm the required contexts on the final head.
5. A read-only Opus 5.5 custody review of `d35633fad` returned **repair**, with two should-fix and six minor findings. Their dispositions are under "Review round" below; reviews 3 and 4 cover the resulting head.

### Step 6 reviews (2026-10-03, head db24156b92ac)

Items 3 and 4 of "Independent review history" and row 7 of "Review round" call these two reviews pending at `b2d556c95`; this section records their returned verdicts at `db24156b92ac`. Findings are condensed to one line each (severity, location, problem) without the reviews' fix proposals. Host paths are replaced by repository-relative paths or `<private path>`. Line numbers cited for the PR body are those of the body as read at 2026-10-03T19:20Z, before this section was added.

**(a) GPT-6 Astra/max cross-family review**, run through the packaged lane, job `rev-489-astra`. Verdict: **repair**. One finding.

- **should-fix** | `docs/decisions/2026-09-28-openhands-resolver-isolation.md:228-236`; `blueprints/runtime-workers/openhands/RESOLVER.md:535-537` | The fork option needs explicit implementation and safety prerequisites. The runbook permits proceeding after either choice, but `gh_harness.py:44-47,257-259,704-708` still targets the upstream repository and rejects a fork push URL, so following it after choosing a fork would use the same-repository path. Forking also leaves networked CI and final-message publication unresolved, while the unconditional token/secrets guarantee is explicitly unverified.

**(b) Opus closure review.** Verdict: **repair**. Nine findings: two should-fix and seven minor.

- **should-fix** | `docs/decisions/2026-09-28-openhands-resolver-isolation.md:227-236`; `blueprints/runtime-workers/openhands/RESOLVER.md:519-521` (also `:420-422`) | The fork option (option 2) is described incompletely, and `RESOLVER.md` files it under the wrong kind of change, so the owner cannot see what it costs or leaves open. (i) `RESOLVER.md:519-521` lists "pushing to an owner fork" among the "workflow changes outside this PR", but it changes this PR's own harness, which only targets this repository (`gh_harness.py:44`, `:241-254`, `:257-259`, `:448`, `:627-628`, `:684-689`, `:704-709`; the record's ruleset section, `:131-178`, binds only this repository's `openhands/*` refs), so option 2 needs harness changes, a ruleset on the fork and its own review before any run, while option 1 needs no code change. (ii) Option 2 (`:230-232`) says nothing about condition 3: `build_pr_body` (`resolver.py:547-550`, `:590-592`) publishes up to 6000 characters of the agent's final message wherever the branch lives, so option 2 leaves that deviation open while option 1 explicitly accepts it, and the record's closing sentence (`:235-236`) implies that accepting either option lets the first live run go ahead. (iii) The fork's owner is not named (the repository is public and owned by a User account; isFork false, forkCount 0), and the record already says GitHub's fork-PR limits were not re-read.
- **should-fix** | PR #489 body (`gh pr view 489 --json body`, read 2026-10-03T19:20Z): lines 5, 6, 8, 46, 52, 149 ("CI on this head"), 151 | The body still describes `b2d556c95`, not the reviewed head `db24156b9`: line 5 (base and head); line 6 (main `ecea2865` "not merged", and the merge list omits `db24156b9`, which merges main `d2fc3803e`, already containing `ecea28654`); line 8 (last full registry pass `d35633fad`); lines 46 and 151 (`d35633fad` as the Linux full-suite evidence, "differs from this head only in the repaired test module"); line 52 and the "CI on this head" paragraph, line 149 (`validate` failed on main's unsorted registry pair). At `db24156b9` the registry has 0 unsorted pairs and `validate` passed: job `111264954786` (head_sha `db24156b9`, CI merge `69650a33` onto `d2fc3803e`) shows `validate.py` `{"hashed_files": 9550, "receipts": 193, "status": "passed"}`, `component_matrix.py --check` `{"rows": 32, "status": "checked"}`, the new-host grand-list check passed, and `python3 -m unittest` "Ran 10078 tests ... OK (skipped=968)". Contract step 8 requires the merged main SHA, the local commands with exit codes and an evidence table for the head that lands.
- **minor** | `tests/test_runtime_worker_openhands_resolver.py:1582-1589` | The probe and the assertions are correct; only the skip message is too narrow. The probe string `f"{self.tmp}/"` (`:1584`) fires exactly when the guard (`outgoing_guard.py:165-170`) would refuse as `private_content` before `host_path`, and no assertion is weakened (`host_path` is still asserted at `:1565` on the synthetic root, the ValueError cases moved unchanged to `:1570-1573` ahead of the skip, and the symlink and realpath assertions at `:1590-1592` are unchanged). The skip message (`:1588-1589`) and the comment (`:1582`) blame "TMPDIR is under a personal home path", but the probe fires on any `scripts/validate.py` `PRIVATE_CONTENT` pattern (`validate.py:25-37`), such as a session-UUID, task-handle or Windows-user-path `TMPDIR`, and then the skip names the wrong cause.
- **minor** | PR #489 body line 175; `blueprints/runtime-workers/openhands/RESOLVER.md:514-516`; order at `blueprints/runtime-workers/openhands/resolver.py:1589-1591` and `host.py:1359-1361` | The body says "G4 remains unrecorded, so a real run refuses with `stage_gate_g4_not_recorded`", but that reason code applies only once the other gates are recorded. Today there is no `<state>/stage-gates.json`: `plan_run` calls `host.verify_stage_gates` first (`resolver.py:1589`), and `read_stage_gates` raises `stage_gates_not_recorded` (`host.py:1360-1361`) before `verify_reviewer_gate` (`resolver.py:1591`) can raise `stage_gate_g4_not_recorded`. The gates themselves are intact: no bypass flag or environment override exists, `_cmd_run` requires `--reviewer-command` (`resolver.py:1663-1664`), and at dispatch start `verify_isolation` (`dispatch.py:319`) re-checks P0-P2 freshness (`host.py:1445-1447`), the stage gates and G5 (`host.py:1463-1464`).
- **minor** | Required context `validate-macos` on `db24156b9` (run `37144290967`, job `111264957003`) | Verification gap, not a defect: `validate-macos` was still pending (queued) at 2026-10-03T19:20:37Z; the other seven required contexts passed on `db24156b9`. The native-macOS evidence the review read is from the earlier head `d35633fad` (artifact `11278304065` of run `37129286224`, `full-suite-macos.log`: "Ran 10070 tests ... OK (skipped=1329)", `test_case_unicode_and_filesystem_aliases_are_refused ... ok`, no FAIL or ERROR in `tests.test_runtime_worker_openhands_resolver`). That confirms the git-plumbing alias fixture on APFS but predates the `TMPDIR` repair `2ede7b871`; the `db24156b9` run is the first native-macOS run of that repair.
- **minor** | Contract step 6(a) (`<private path>:71`); PR #489 review state | Verification gap, not a defect: the separate read-only GPT-6.1 Astra/max cross-family review was still pending when this review ran; GraphQL at 19:20Z showed 0 review threads and 0 reviews on the PR.
- **minor** | `origin/main` `1f5a791b02a230aced670c88bab3d3d0ebcf401a` versus the merged base `d2fc3803e01178b4687771d0bf91faf453bb6933`; `manifests/evidence.json` | Verification gap, not a defect: main moved after the custody merge (#640, #648). Three registry rows changed: `docs/harness-defaults.md`, `observability/grand-dashboard/state.json` and `docs/token-session-handbook.md`. Their hunks do not overlap this PR's `evidence.json` hunks, but the landing head no longer merges current main.
- **minor** | Contract step 5 commands at `db24156b9` | Verification gap, not a defect: the review ran no acceptance command (it had no Bash; those are the coordinator's commands). Not re-run at `db24156b9`: the unit-test pair under a neutral `TMPDIR` and a home-path `TMPDIR`, the planted-defect mutations, `resolver.py --help` and `run --help`, `git diff --check` and the pre-push gitleaks scan. The body records them only at `b2d556c95`. CI on `db24156b9` covers the Linux full suite, `validate.py`, the generator checks and secret-scan.
- **minor** | Items 3-4 and the safety boundary, verified at `db24156b9` | No defect (verification record). Scope: `d2fc3803e..db24156b9` is exactly the 17 allowed paths (`README.md`, `.github/` and `blueprints/us-equities/` untouched; owned files equal `b2d556c95`). Hot-file merge: all 9541 rows of main's `evidence.json` kept in order, 9 new and 7 updated owned rows added, all 16 sha256/bytes values match HEAD, `receipts[]` and `convergence_records[]` equal main's, `files[]` sorted with no duplicates. Preserved: both fail-first blobs (`ed55377f`, `3f41c01b`), decision-record lines 1-15 (match `501bcc9e` and main), the amendment heading at `:208`, the skill-contract fix (`host.py:408-413`, `e2e/task.py:46-56`) and the A7 env-name read in teardown (`host.py:1095`). At the merged base the 11 `pull_request` workflows still declare `contents: read` and use no secrets, and the SARIF upload jobs still skip `pull_request`. `fold()`, `HFS_IGNORABLE` and the alias rules (`patch_policy.py:116-129`, `:893-897`, `:953-958`) decide from git data, and the outgoing guard's check order and 0600 `O_EXCL|O_NOFOLLOW` body files are intact.

### SOTA sources

- [git/git `v2.43.0`](https://github.com/git/git/tree/v2.43.0), matching installed Git 2.43.0: [the native index-fixture reference](https://github.com/git/git/blob/v2.43.0/t/t2107-update-index-basic.sh#L59), [git-hash-object(1)](https://github.com/git/git/blob/v2.43.0/Documentation/git-hash-object.txt#L18), [git-update-index(1)](https://github.com/git/git/blob/v2.43.0/Documentation/git-update-index.txt#L75), and [index insertion implementation](https://github.com/git/git/blob/v2.43.0/builtin/update-index.c#L421). The additional macOS requirement follows [git.c's argument conversion](https://github.com/git/git/blob/v2.43.0/git.c#L449), [precompose_utf8.c](https://github.com/git/git/blob/v2.43.0/compat/precompose_utf8.c#L67), and [core.precomposeUnicode](https://github.com/git/git/blob/v2.43.0/Documentation/config/core.txt#L44). Installed help, versioned release notes and these primary sources were read on 2026-10-03. Earlier resolver patch handling also follows `git-apply(1)`, `git-diff(1)` and `git-merge-base(1)` at this revision.
- [Gitleaks `v8.30.1`](https://github.com/gitleaks/gitleaks/tree/v8.30.1): [release notes](https://github.com/gitleaks/gitleaks/releases/tag/v8.30.1), [native Git-range and directory commands](https://github.com/gitleaks/gitleaks/blob/v8.30.1/README.md#L196), and installed CLI help. The sandbox tests select the same installed upstream binary directly because the host wrapper's lock directory is outside the writable sandbox.
- Existing resolver implementation references: [OpenHands/extensions `bea7a20`](https://github.com/OpenHands/extensions/tree/bea7a20), `skills/github-issue-to-pr/scripts/main.py` (branch naming and loop) and `skills/github-pr-reviewer/scripts/worker.py`; [OpenHands/software-agent-sdk `fcc102a`](https://github.com/OpenHands/software-agent-sdk/tree/fcc102a), v1.49.6; [OpenHands/OpenHands `7bc33009`](https://github.com/OpenHands/OpenHands/tree/7bc33009), the retired resolver comparison. These are the original implementation's historical reviewed pins; the custody change adds no runtime or orchestration alternative.
- [cli/cli `v2.101.0`](https://github.com/cli/cli/tree/v2.101.0) (`0cf10924`), including `pkg/cmd/pr/checks/aggregate.go`, credential-helper behavior and fixed `api`/PR operations; [GitHub create-review documentation](https://docs.github.com/en/rest/pulls/reviews#create-a-review-for-a-pull-request) (`commit_id`) and [GraphQL issue/edit provenance](https://docs.github.com/en/graphql/reference/objects#issue), read in the original 2026-09-28/29 implementation review; CPython `v3.12.3`, `importlib._bootstrap_external.FileFinder`, for package-before-module resolution.
- Repository: `docs/decisions/2026-09-28-openhands-resolver-isolation.md`, `docs/lanes.md`, `.github/pull_request_template.md`, the merged #425 recipe, #429's runtime skill exclusion and #465's agent-branch ruleset. The registry follows the hot-file protocol: main's copy at each merge, then `scripts/host_receipts.py:register_file` for the owned rows and the supported report generators.
- Review-round fixture repair: [git/git `v2.43.0` `t/test-lib-functions.sh`](https://github.com/git/git/blob/v2.43.0/t/test-lib-functions.sh#L750) (`test_lazy_prereq`, a probed prerequisite; filesystem examples such as `SYMLINKS` and `CASE_INSENSITIVE_FS` in [`t/test-lib.sh`](https://github.com/git/git/blob/v2.43.0/t/test-lib.sh#L1773)), and [CPython `v3.13.15` `Lib/unittest/case.py`](https://github.com/python/cpython/blob/v3.13.15/Lib/unittest/case.py#L54) (`_Outcome.testPartExecutor` records a `SkipTest` raised inside `subTest` as that subtest's skip; `subTest`, `:538-565`, resumes after the block and stops the method under failfast). Read on 2026-10-03; the installed interpreter's `case.py` is byte-identical to that tag.
- Step 6 repair round: repository source read at `b0c11c324`, covering `resolver/gh_harness.py` (`REPO`, `op_push`, `push`, `op_pr_create` and its allowlist entry, `check_repository` and `branch_rules`), `resolver.py` `build_pr_body` and `plan_run`, and `host.py` `read_stage_gates`. The repository's owner type, visibility and fork count were read with `gh api` on 2026-10-03. GitHub's fork-PR token and secret limits come from [Events that trigger workflows, "Workflows in forked repositories"](https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows), for `pull_request` runs from a fork. [Forks, "Which repositories can be forked?"](https://docs.github.com/en/pull-requests/reference/forks) says nothing on forking one's own repository, so the decision record marks the fork's holder undetermined. Both pages were read 2026-10-03 and re-read 2026-10-04T00:23Z.
- Pre-push gate (2026-10-04). The GPT-family job 004's six sources, which also cover the command center's proposal, were each fetched on 2026-10-04 (HTTP 200) and quoted in the decision record:
  - [GitHub Secure use reference](https://docs.github.com/en/actions/reference/security/secure-use): "Any user with write access to your repository has read access to all secrets configured in your repository", plus untrusted checkout and hosted runners;
  - [Workflow syntax](https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax): unspecified permissions are set to none, `cache-mode`, `steps[*].run`, `working-directory` and local `uses: ./`;
  - [Events that trigger workflows](https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows): `push` "includes workflows that are not merged into the default branch", and the fork limits;
  - [Dependency caching reference](https://docs.github.com/en/actions/reference/workflows-and-actions/dependency-caching): PR runs restore base and default-branch caches, and their own caches are scoped to the merge ref;
  - [OpenSSF Scorecard checks](https://github.com/ossf/scorecard/blob/main/docs/checks.md): Token-Permissions and Dangerous-Workflow;
  - [zizmor audits](https://docs.zizmor.sh/audits/): the five audits the gate fails on, each working offline.
  The gate's mechanisms add three more: [GitHub Script injections](https://docs.github.com/en/actions/concepts/security/script-injections) (the untrusted-context endings), [Python unittest, "Test Discovery"](https://docs.python.org/3/library/unittest.html#test-discovery) with the installed CPython 3.13.15 `unittest/loader.py`, and the installed zizmor 1.30.1 `--help`. In-repository references: `patch_policy.py`'s reviewed derivation, `tests/test_workflow_hardening.py`'s text-level workflow reading, and `.github/requirements-ci.txt` and `validate.yml` for CI's zizmor pin and flags.
- Gate data (cross-family repair, 2026-10-04). The Python behaviour the reader models, from docs.python.org/3.13 (read 2026-10-04, HTTP 200) and checked on the installed CPython 3.13.15: [pathlib](https://docs.python.org/3.13/library/pathlib.html) ("If a segment is an absolute path, all previous segments are ignored (like os.path.join())"; `Path.rglob`), [fnmatch](https://docs.python.org/3.13/library/fnmatch.html) ("the filename separator ('/' on Unix) is not special to this module"), [tomllib](https://docs.python.org/3.13/library/tomllib.html) and [csv](https://docs.python.org/3.13/library/csv.html) (read through a file object), and the comprehension scopes of the [Language Reference 6.2.4](https://docs.python.org/3.13/reference/expressions.html#displays-for-lists-sets-and-dictionaries) and [PEP 572](https://peps.python.org/pep-0572/). In-repository: `scripts/validate_convergence.py` (P1's example) and `tools/sota-convergence/gap_wave_ledger.py` (the main source of breadth).

### Evidence-class table

| Claim | Evidence class | Command / receipt |
| --- | --- | --- |
| Resolver templates, patch policy, outgoing guard, PR loop, host mode and end-to-end fake scenarios | `local_integration` | Required OpenHands test pair at `b0c11c324` with a neutral `TMPDIR`: 248 tests, exit 0. It uses fake Docker, GitHub and agent-server operations and real local Git |
| The pair no longer depends on `TMPDIR` | `local_integration` | The same pair at `b0c11c324` with a throwaway `TMPDIR` under the host's home path: exit 0, `OK (skipped=2)`. The two symlink subtests are skipped by the probed prerequisite with the reworded reason. At `d35633fad` the same run exited 1 with failures=2 (`'private_content' != 'host_path'`) |
| The repaired guard assertions still catch planted defects | `synthetic`, at `2ede7b871` | Scratch worktree at `2ede7b871`. With the `host_path` refusal disabled, both tests fail under both `TMPDIR`s (exit 1). With the realpath form dropped, the symlink subtest fails under a neutral `TMPDIR` (exit 1) and is skipped under a home-path `TMPDIR`. Not re-run at `b0c11c324`: `cf42c6d08` changes only that test's comment and skip text, not an assertion |
| The previous alias fixture fails when its three names collapse | `synthetic` | Existing unittest with only the three alias writes remapped: three matching failed assertions, exit 1 before repair; exit 0 after repair |
| All seven alias paths reach the unchanged validator and retain every refusal check | `local_integration` | All 11 `PatchValidatorTests` inside the pair run at `b0c11c324`, exit 0 on Linux with real Git 2.43.0; no platform skip |
| The repaired alias fixture and the `TMPDIR` repair pass on native macOS | `source_review` of retained CI execution output | `db24156b9`: `validate-macos` job `111264957003` (run `37144290967`), artifact `full-suite-macos.log`: `Ran 10078 tests`, `OK (skipped=1329)`, no FAIL or ERROR block. `test_case_unicode_and_filesystem_aliases_are_refused`, `test_host_paths_and_the_user_name_are_refused` and `test_pr_body_follows_the_template_and_renders_model_text_inert` are each `ok`. This is the first native macOS run of `2ede7b871`. Earlier, `d35633fad`'s job `111220987307` passed the alias test. `b0c11c324`: pending |
| The old native macOS suite failed at exactly the three alias subtests | `source_review` of retained historical execution output | Run `36524134513` (head `501bcc9e`), job `109263298833`, artifact `11015337319`, `full-suite-macos.log`: 7339 tests, failures=3, skipped=959. Its three FAIL blocks are the subtests `docs/A.md`, `Docs/z.md` and `docs/café.md` at test line 725, each `('case_or_unicode_alias', path) not found`. Re-downloaded read-only in the custody review round |
| The original tests catch planted defects | `synthetic`, historical | 9 of 9 repair-round mutations detected; unchanged `evidence/stage2-*fail-first.txt` logs |
| Git's macOS argument precomposition needs an explicit fixture override | `source_review` | git/git `v2.43.0` `git.c:449`, `compat/precompose_utf8.c:67-105`, `Documentation/config/core.txt:44-51` |
| The resolver as built pushes to and opens PRs only in this repository, so the fork option needs a harness change | `source_review` | At `b0c11c324`: `resolver/gh_harness.py:44`, `:241-254`, `:257-259`, `:448`, `:627-628`, `:684-689`, `:704-709`; the final message reaches the PR body through `resolver.py:547-550`, `:590-592` |
| A real run refuses at the gates stage before G4 today | `source_review` | At `b0c11c324`: `plan_run` checks the stage gates, G5 and then G4 (`resolver.py:1589-1591`). `read_stage_gates` raises `stage_gates_not_recorded` when `stage-gates.json` is absent (`host.py:1357-1361`) |
| The original workflow/token bounds | `source_review`, historical scope | Stage 2 security review of 20 workflows at `94894f54`; this is not current-base CI or fork acceptance |
| Linux full suite in CI | `source_review` of retained CI execution output | `db24156b9`: `validate` job `111264954786`, CI merge `69650a33` of `db24156b9` into `d2fc3803e`, `python3 -m unittest`: `Ran 10078 tests in 1664.615s`, `OK (skipped=968)`. `b0c11c324`: pending |
| Linux full suite on the host | `local_integration`, before the step 6 round | Registry commit `96b96c681` (only `.github/workflows/validate.yml` and `manifests/evidence.json` differ at `d35633fad`), home-path `TMPDIR`: exit 1; 10,070 tests, failures=12, errors=1, skipped=850. Its 13 failing IDs are the 2 fixture cases repaired in `2ede7b871` and the 11 compared in the next row. Not re-run at `b0c11c324`: it takes about 43 minutes on this host, which would overrun a scheduled measurement window. The builder's sandbox run (10,046 tests, 485 failures) is superseded |
| None of the 11 remaining IDs is specific to this PR | `local_integration`, at `b2d556c95` | Same command and `TMPDIR` at `b2d556c95`, in a fresh detached worktree at origin/main `ecea2865`, and at the then-merged base `59f8a1e36`. With a neutral `TMPDIR`, the same 1 failure on every tree; with a home-path `TMPDIR`, the same 9 failures on every tree. Supersedes the builder's 607-method archive comparison and the earlier `4ced2923` clone control |
| Publication validation | `local_integration` | `scripts/validate.py` with a neutral `TMPDIR` at `b0c11c324`: exit 0, `"status": "passed"` (9,550 hashed files, 193 receipts). CI's `validate.py` passed on `db24156b9` (job `111264954786`). The builder's exit 1 (the `AGENTS.md` mismatch before the `4ced2923` merge) is superseded |
| Registry rebuild and generated reports | `local_integration` | At `db24156b9`: all 16 owned rows over main `d2fc3803e`'s registry. At `b0c11c324`: the rows of the record, `RESOLVER.md` and the resolver test module are refreshed. Both generators exit 0 and change no file, and `scripts/evidence_manifest.py --check` passes (9,550 files) |
| Configured Gitleaks scan of history | `local_integration` | Gitleaks 8.30.1 over `origin/main..HEAD` at `b0c11c324`: 36 commits, exit 0, no leaks found. The pre-commit scans of both step 6 commits found no leaks |
| Required contexts on the pushed head | `source_review` of CI status | `db24156b9`: all eight passed (`validate` job `111264954786`, `validate-macos` job `111264957003`). A later `validate` re-run there (job `111278594671`) was cancelled at 19:56:39Z, after `b0c11c324` was pushed. `b0c11c324` at 19:58:50Z: dependency-review, osv-scanner, sota-sources, token-report and verdict-review-gate passed; validate and secret-scan were in progress; validate-macos was queued |
| The pre-push gate refuses planted protected changes before any push, passes a benign commit, refuses from inside the agent tree and fails closed without zizmor | `local_integration` | `tests.test_runtime_worker_openhands_push_gate` at `050bca5d5`: 31 tests, exit 0, `OK (skipped=1)`, the PyYAML cross-check, which this interpreter cannot run; its real-zizmor 1.30.1 test ran |
| The gate's workflow reader matches PyYAML on all 21 workflows | `local_integration` | `/usr/bin/python3` (PyYAML 6.0.1) `-m unittest ...push_gate.RepositoryWorkflowTests`: 3 tests, exit 0 |
| The new and updated tests fail without the gate | `synthetic`, failing-first | Base `456f8fee6` with the new tests copied in: gate module exit 1 (errors=7), resolver module exit 1 (failures=1, errors=56), each traced to the missing gate API (`evidence/push-gate-fail-first.txt`, part 1) |
| The gate's tests catch planted defects | `synthetic` | 17 mutations of `push_gate.py` and `gh_harness.py`, each failing its named tests (exit 1); the unmutated copy passes (part 2) |
| The gate on this repository's own trees, with real zizmor | `local_integration`, rehearsal | Local clones of `48831bc65` with one planted commit each, real zizmor 1.30.1, no resolver, container or GitHub call. The benign edit passes. Workflow, CODEOWNERS, gate-script, helper, policy-test, new-test, `.gitleaks.toml` and gate-code edits are refused with their rules, and a planted template injection is refused by zizmor. About 2 s per check (part 4) |
| Live resolver containment, model/gateway behavior, GitHub writes | not run / not accepted | The CI posture is decided (option 1 with the trusted pre-push gate). The first live run waits until the gate lands on main, its negative controls pass there, and G2/P3/G5 and G4 are recorded with fresh P0-P2. No live resolver run occurred |

### Local commands run

```text
# Head 050bca5d5 (pre-push gate), 2026-10-04 04:00-04:02Z. TMPDIR=/var/tmp/489-gate, nice -n 19, after
# git fetch origin main (origin/main aecfaaaa6, merge base d2fc3803e). The tree was clean at this head.
$ python3 -m unittest tests.test_runtime_worker_openhands_resolver tests.test_runtime_worker_openhands
exit 0; Ran 249 tests in 20.319s; OK
$ python3 -m unittest -v tests.test_runtime_worker_openhands_push_gate
exit 0; Ran 31 tests in 8.958s; OK (skipped=1: the PyYAML cross-check; the real-zizmor test ran)
$ /usr/bin/python3 -m unittest tests.test_runtime_worker_openhands_push_gate.RepositoryWorkflowTests
exit 0; Ran 3 tests; OK (PyYAML 6.0.1 cross-check of the workflow reader)
$ python3 scripts/validate.py
exit 0; {"components": 69, "hashed_files": 9553, "profiles": 4, "receipts": 193, "status": "passed"}
$ python3 scripts/evidence_manifest.py --check
exit 0; {"files": 9553, "status": "passed"}
$ python3 scripts/component_matrix.py --check; python3 scripts/new_host_grand_list.py --check   # before the registry commit
exit 0 {"rows": 32, "status": "checked"}; exit 0 {"status": "passed", "layers": 32, "winners": 66}; no --write needed
$ git diff --check origin/main...HEAD
exit 0
$ python3 blueprints/runtime-workers/openhands/resolver.py --help; ... run --help
exit 0; exit 0 (run --help lists --zizmor)
$ gitleaks git . --config .gitleaks.toml --log-opts=origin/main..HEAD --redact --timeout 600
exit 0; 44 commits scanned; no leaks found (the four commits' pre-commit scans: no leaks)
$ python3 <the coordinator's merge-tree landing check> aecfaaaa6 050bca5d5
exit 0; clean three-way merge; merged-vs-main paths 20, outside PR-owned 0; main drift 304 paths, overlap 0;
registry foreign rows equal, order preserved, PR-owned rows 19; merged files[] sorted, no duplicates (9735 rows); LANDABLE
$ git push origin HEAD:claude/openhands-resolver-20260928
exit 0; pre-push registry tests: Ran 3 tests, OK; 456f8fee6..050bca5d5, no force
Failing-first, planted-defect and rehearsal runs: blueprints/runtime-workers/openhands/evidence/push-gate-fail-first.txt.
Not run: the host full suite (about 43 minutes here); CI runs it on the pushed head.

# Head b0c11c324 (step 6 repair round), 2026-10-03 19:53-19:56Z. TMPDIR is a neutral
# directory outside the home directory and every repository, unless the line says
# home-path TMPDIR. The commands ran on the working tree, which was then committed
# unchanged as cf42c6d08 and b0c11c324.
$ git diff --check
exit 0
$ nice -n 19 python3 -c '<host_receipts.register_file(Path("."), p) for each path>' <the record> <RESOLVER.md> <the resolver test module>
exit 0
$ nice -n 19 python3 scripts/component_matrix.py --write
exit 0; {"flip_rule_violations": 0, "rows": 32, "status": "written"}; no file changed
$ nice -n 19 python3 scripts/new_host_grand_list.py --write
exit 0; {"status": "written", "layers": 32, "winners": 66}; no file changed
$ nice -n 19 python3 scripts/evidence_manifest.py --check
exit 0; {"files": 9550, "status": "passed"}
$ nice -n 19 python3 scripts/validate.py
exit 0; {"components": 69, "hashed_files": 9550, "profiles": 4, "receipts": 193, "status": "passed"}
$ nice -n 19 python3 -m unittest tests.test_runtime_worker_openhands_resolver tests.test_runtime_worker_openhands
exit 0; Ran 248 tests in 22.014s; OK
$ (home-path TMPDIR, a throwaway directory removed afterwards) nice -n 19 python3 -m unittest -v <the same pair>
exit 0; Ran 248 tests in 24.993s; OK (skipped=2); both skips give the reworded reason
$ nice -n 19 python3 blueprints/runtime-workers/openhands/resolver.py --help
exit 0
$ nice -n 19 python3 blueprints/runtime-workers/openhands/resolver.py run --help
exit 0
$ nice -n 19 git diff --check origin/main...HEAD
exit 0 (origin/main 463a57b98, merge base d2fc3803e); git diff --name-only origin/main...HEAD: the 17 contract paths
$ cmp <(git show 501bcc9e:<record> | sed -n 1,15p) <(sed -n 1,15p <record>)    # and the same against origin/main
exit 0; exit 0
$ git rev-parse HEAD:<log> 501bcc9e:<log>    # both evidence/stage2-*fail-first.txt logs
ed55377f232aa64f46f1b7dce004fce1be5cc7a7 and 3f41c01b05feff7bf199c16266ea96c73ffa14c5, equal at both commits
$ nice -n 19 gitleaks git . --config .gitleaks.toml --log-opts=origin/main..HEAD --redact --timeout 600
exit 0; 36 commits scanned; no leaks found (the pre-commit scans of cf42c6d08 and b0c11c324: no leaks)
$ nice -n 19 python3 <the coordinator's merge-tree landing check> 463a57b98 b0c11c324
exit 0; clean three-way merge; merged-vs-main paths 17, outside PR-owned 0; main drift 30 paths, overlap 0;
registry foreign rows equal, order preserved, PR-owned rows 16; merged files[] sorted, no duplicates (9571 rows); LANDABLE
$ git push origin HEAD:claude/openhands-resolver-20260928
exit 0; pre-push registry tests: Ran 3 tests, OK; db24156b9..b0c11c324, no force
Not run in this round: the host full suite (about 43 minutes here, which would overrun a scheduled
measurement window; the CI full suites on db24156b9 are below) and the planted-defect mutations
(recorded at 2ede7b871; no assertion has changed since).

# Head b2d556c95, 2026-10-03 17:08-17:10Z (historical). Same TMPDIR convention.
$ nice -n 19 python3 scripts/validate.py
exit 0; {"components": 69, "hashed_files": 9429, "profiles": 4, "receipts": 187, "status": "passed"}
$ nice -n 19 python3 -m unittest tests.test_runtime_worker_openhands_resolver tests.test_runtime_worker_openhands
exit 0; Ran 248 tests in 19.862s; OK
$ (home-path TMPDIR) nice -n 19 python3 -m unittest tests.test_runtime_worker_openhands_resolver tests.test_runtime_worker_openhands
exit 0; Ran 248 tests in 20.310s; OK (skipped=2)
$ nice -n 19 python3 blueprints/runtime-workers/openhands/resolver.py --help
exit 0
$ nice -n 19 python3 blueprints/runtime-workers/openhands/resolver.py run --help
exit 0
$ nice -n 19 git diff --check origin/main...HEAD
exit 0 (origin/main ecea2865, merge base 59f8a1e36)
$ git diff --name-only origin/main...HEAD
exit 0; exactly the 17 contract paths
$ nice -n 19 gitleaks git . --config .gitleaks.toml --log-opts=origin/main..HEAD --redact --timeout 600
exit 0; 34 commits scanned; no leaks found
$ nice -n 19 python3 scripts/component_matrix.py --write
exit 0; {"flip_rule_violations": 0, "rows": 32, "status": "written"}; no file changed
$ nice -n 19 python3 scripts/new_host_grand_list.py --write
exit 0; {"status": "written", "layers": 32, "winners": 66}; no file changed
$ git push origin HEAD:claude/openhands-resolver-20260928
exit 0; pre-push registry tests: Ran 3 tests, OK; d35633fad..b2d556c95, no force

# Head d35633fad, before the fixture repair, 16:55-16:57Z
$ nice -n 19 python3 scripts/validate.py
exit 0; "status": "passed"
$ nice -n 19 python3 -m unittest tests.test_runtime_worker_openhands_resolver tests.test_runtime_worker_openhands
exit 0; Ran 248 tests in 20.274s; OK
$ (home-path TMPDIR) the same pair
exit 1; Ran 248 tests in 20.300s; FAILED (failures=2)
OutgoingGuardTests.test_host_paths_and_the_user_name_are_refused and
PullRequestLoopTests.test_pr_body_follows_the_template_and_renders_model_text_inert: 'private_content' != 'host_path'

# Clean-main comparison of the 11 non-OpenHands IDs that failed in the host full suite below
$ git worktree add --detach <scratch> origin/main      # ecea28654a835fff2cc3651bab77ca0e46b9bec5, clean
$ git -C <scratch> checkout --detach 59f8a1e36          # the then-merged base, clean
$ nice -n 19 python3 -m unittest <the 11 IDs>          # same command in every tree and TMPDIR
tree                     neutral TMPDIR          home-path TMPDIR
b2d556c95                exit 1; failures=1      exit 1; failures=9
d35633fad                exit 1; failures=1      exit 1; failures=9
origin/main ecea2865     exit 1; failures=1      exit 1; failures=9
merged base 59f8a1e36    exit 1; failures=1      exit 1; failures=9
Within each TMPDIR, the failing IDs are identical on every tree:
- under both: tests.test_windows_terminal_defaults.OverlayTests.test_the_installed_client_knows_no_notification_type_without_a_decision
  (the installed client knows a notification type, auth_storage_failure, that has no decision)
- under the home-path TMPDIR only, in tests.test_token_e2e_grader:
  F19c_Stage3Mutants.test_M17c_manifest_canary_off, F19d_RepairRoundMutants.test_M56c_the_export_backstop_is_off,
  F29_Export (3 tests), F29b_CheckHtml (2 tests), F29c_ArgvSanitizer.test_a_grade_run_that_spells_its_flags_with_equals_records_no_path
- passed on every tree under both:
  tests.test_gpt6_family_tiering_20260926.RunnerTests.test_sigterm_records_the_running_call_as_interrupted_without_counting_it,
  tests.test_order_throughput.CapacityRunTests.test_cli_refuses_live_base_url_from_env_file_without_network
$ git worktree remove <scratch>; git worktree prune
exit 0

# Host full suite, registry commit 96b96c681, home-path TMPDIR (coordinator run before the custody review round)
$ nice -n 19 python3 -m unittest
exit 1; Ran 10070 tests in 2568.426s; FAILED (failures=12, errors=1, skipped=850)
13 failing IDs: the 2 OpenHands fixture cases (repaired in 2ede7b871) and the 11 IDs compared above
earlier control, superseded by the comparison above: the 11 IDs in a clean 4ced2923 clone, FAILED (failures=10, errors=1)

# CI, read-only, 2026-10-03T19:57-19:59Z (gh api jobs and check-runs, gh run view --log, gh run download)
db24156b9 validate job 111264954786: success. CI merge 69650a33 (db24156b9 into d2fc3803e);
validate.py {"components": 69, "hashed_files": 9550, "profiles": 4, "receipts": 193, "status": "passed"};
component matrix {"rows": 32, "status": "checked"}; new-host grand list {"status": "passed", "layers": 32, "winners": 66};
python3 -m unittest: Ran 10078 tests in 1664.615s; OK (skipped=968)
db24156b9 validate-macos job 111264957003: success at 19:29:18Z. full-suite-macos.log: Ran 10078 tests in 1794.871s;
OK (skipped=1329); no FAIL or ERROR block; the alias test and both TMPDIR-repair tests ok
db24156b9 required contexts: all eight success; a later validate re-run (job 111278594671) was cancelled at
19:56:39Z, after b0c11c324 was pushed
b0c11c324 at 19:58:50Z: dependency-review, osv-scanner, sota-sources, token-report and verdict-review-gate success;
validate and secret-scan in progress; validate-macos queued

# CI, read-only, historical
$ gh pr checks 489 --required        # 2026-10-03T17:16:15Z
b2d556c95: dependency-review, osv-scanner, secret-scan, sota-sources, token-report and verdict-review-gate pass;
validate fail, job 111250877246: "files[2392]: files[] must be sorted by path (found
'docs/decisions/2026-10-03-omniroute-sdk-worker-0160.md' after
'docs/decisions/2026-10-03-retire-pr320-loki-denominator-host-receipts.md')", the pair main's job 111240392112 reports;
validate-macos pending
d35633fad: all eight passed: dependency-review, osv-scanner, secret-scan, sota-sources, token-report,
validate (29m39s), validate-macos (36m58s), verdict-review-gate
d35633fad validate job 111220987213: Ran 10070 tests in 1679.211s; OK (skipped=968)
d35633fad validate-macos job 111220987307, full-suite-macos.log: Ran 10070 tests in 1849.584s; OK (skipped=1329)

# Builder's sandbox record, before the 4ced2923 and 59f8a1e36 merges (superseded where marked).
# Its first pair run, with the host Gitleaks wrapper, exited 1 on the wrapper's unavailable lock;
# that failed attempt is retained separately, and PATH then selected native Gitleaks 8.30.1.
$ nice -n 19 python3 -m unittest tests.test_runtime_worker_openhands_resolver.PatchValidatorTests
exit 0; Ran 11 tests in 1.185s; OK
$ nice -n 19 python3 -m unittest        # superseded by the host and CI runs above
exit 1; Ran 10046 tests; FAILED (failures=485, errors=195, skipped=863); errors=168 with native Gitleaks on PATH
$ nice -n 19 gitleaks git . --config .gitleaks.toml --pre-commit --redact --timeout 600
exit 0; actual working diff scanned; no leaks found
$ nice -n 19 python3 scripts/validate.py   # superseded: the 4ced2923 merge brought main's AGENTS.md
exit 1; only the AGENTS.md SHA-256 and byte-count mismatch
```

These are repository integration checks, not unchanged upstream acceptance suites.

**CI on this head.** The workflows test GitHub's merge of the head with main.

On the reviewed head `db24156b9`, all eight required contexts passed:
- `validate` job `111264954786` tested merge `69650a33` (`db24156b9` into `d2fc3803e`). `validate.py` reported `"status": "passed"` with 9,550 hashed files and 193 receipts. The component matrix and new-host grand list checks passed. `python3 -m unittest` ran 10,078 tests: `OK (skipped=968)`.
- `validate-macos` job `111264957003` passed. Its `full-suite-macos.log` shows 10,078 tests, `OK (skipped=1329)` and no FAIL or ERROR block.

The merged registry is sorted. The `b2d556c95` `validate` failure (job `111250877246`) on main's unsorted registry pair no longer applies.

The new head `b0c11c324` changes only the decision record, `RESOLVER.md`, one test's comment and skip text, and three registry rows. Its required contexts were still running when this description was written. The head that lands must show all eight SUCCESS, and a macOS full-suite artifact with no failure in `tests.test_runtime_worker_openhands_resolver`.

**Linux full-suite acceptance item.**
- CI's Linux and macOS full suites passed on `db24156b9`, which differs from `b0c11c324` only in the files listed above.
- On the host, the PR's two test modules pass at `b0c11c324` under both `TMPDIR`s.
- The host full suite was not re-run in this round. At `b2d556c95`, its 11 other failing IDs failed identically on clean main and on the then-merged base, environment by environment.

### Review round

The custody review of `d35633fad` (independent Opus 5.5, read-only) returned **repair**. This is its one repair round. Numbers are the findings' indices in the review record, counted from 0, as in commit `2ede7b871`'s message.

| # | Severity | Finding | Disposition |
| --- | --- | --- | --- |
| 0 | should-fix | The description still described the state before the custody merge (`4ced2923` unmerged, `validate.py` exit 1) | Fixed. Scope, the SOTA "Repository" bullet, the evidence table and the commands now state the merged state: base `59f8a1e36` (merged in `d35633fad`), with `4ced2923` merged in `85830e815`. `scripts/validate.py` with a neutral `TMPDIR`: exit 0, `"status": "passed"` at `d35633fad` and `b2d556c95`. The builder's exit 1 remains only as superseded history |
| 1 | should-fix | The Linux full-suite state was contradictory, with no recorded clean-main comparison | Fixed. "Local commands run" records the comparison: a fresh detached worktree, at origin/main `ecea2865` and then at the merged base `59f8a1e36`, the 11 IDs, the command and every exit code under two `TMPDIR`s. The failing sets match this head's. The builder's sandbox numbers are marked superseded, and the "remains an acceptance blocker" paragraph is replaced by the evidence summary |
| 2 | minor | "Including unittest's trailing spaces" is false for the fail-first logs | Fixed. The clause is removed. The blob SHAs `ed55377f` and `3f41c01b` are unchanged from `501bcc9e`. The same wording in the custody contract, which lives outside the repository, is reported to the coordinator |
| 3 | minor | The home-path `TMPDIR` failure was called an environment artifact, but the fixtures and the check order come from this PR | Fixed in `2ede7b871` (tests only), with its registry row in `b2d556c95`. A synthetic absolute host root serves the two `host_path` assertions. The symlink case probes its prerequisite, following git's `test_lazy_prereq` pattern, and skips its two subtests with an explicit message. No assertion or reason label changed. Fail-first at `d35633fad`: exit 1, failures=2. After the fix: exit 0 under both `TMPDIR`s. Two planted defects are caught (see the evidence table) |
| 4 | minor | `validate-macos` had not run on `d35633fad` | Closed for `d35633fad`: job `111220987307` passed, and its log shows `OK (skipped=1329)` with the alias test `ok`. On `b2d556c95`, `validate-macos` is pending and `validate` failed on main's registry order; see "CI on this head" |
| 5 | minor | The reviewer did not re-run the local acceptance commands | Closed: the final-head runs and their exit codes are recorded above |
| 6 | minor | The reviewer did not download the historical macOS artifact | No change needed. Artifact `11015337319` was re-downloaded read-only in this round, and its three FAIL blocks are cited in the evidence table |
| 7 | minor | Neither contract review has a recorded verdict | Open and listed as pending: 6(a), GPT-6.1 Astra/max, after 19:03Z when the GPT-free slot ends; 6(b), the Opus closure review of `b2d556c95`. The PR had no review threads at 17:16Z |

### Step 6 repair round

This is the one repair round for the two step 6 reviews of `db24156b9`. A is the GPT-6 Astra/max review, and O1-O9 are the Opus closure review's findings in the order listed under "Step 6 reviews". The fixes are in `cf42c6d08`, with their registry rows in `b0c11c324`.

| # | Severity | Finding | Disposition |
| --- | --- | --- | --- |
| A | should-fix | Fork option prerequisites; the runbook permits a run after either choice; CI egress and final message; the fork guarantee is unverified | **Fixed.** The record's option 2 now states that the harness pushes to and opens PRs only in this repository, and lists what option 2 needs before a first run: a fork remote and push-URL check, `--head <owner>:<branch>`, the rules lookup and a `non_fast_forward` ruleset on the fork, and its own review. CI egress and condition 3 are addressed under both options, and the "not re-read" caveat is kept. The `RESOLVER.md` runbook precondition now also stops for option 2 until that change lands. The amendment heading, record lines 1-15 and the live-run wait are unchanged |
| O1 | should-fix | Option 2 incomplete; filed under the wrong kind of change; condition 3; the fork's owner | **Fixed** with A. `RESOLVER.md` Residuals separate the egress block (a workflow change outside this PR) from the fork option (a change to this PR's harness). The record names the fork's owner: the repository's owning User account, which is also the admin login the resolver acts through. The amendment summary in `RESOLVER.md` is updated to match |
| O2 | should-fix | The PR body describes `b2d556c95` | **Fixed** in this description: base and head, the merge list, "CI on this head", the evidence rows, and the local commands at `b0c11c324` with exit codes |
| O3 | minor | The skip message is too narrow | **Fixed.** The comment and skip reason name any `scripts/validate.py` `PRIVATE_CONTENT` pattern, such as a personal home path. No assertion changed, and the reworded reason appears in the home-path `TMPDIR` run |
| O4 | minor | The G4 reason-code order is wrong | **Fixed** in the `RESOLVER.md` G4 residual and under "What is not done" below |
| O5 | minor | `validate-macos` on `db24156b9` | **Closed for `db24156b9`:** job `111264957003` passed, and its artifact is cited in the evidence table. **Open:** `validate-macos` on `b0c11c324` |
| O6 | minor | The 6(a) review is pending | **Closed:** 6(a) returned (A above). **Open:** the reviewers' re-read of this delta |
| O7 | minor | Main moved after the custody merge | **Open, not merged in this round.** The landing check against `463a57b98` reports LANDABLE. Contract step 7's "repeat steps 2 and 7" remains the coordinator's call before landing |
| O8 | minor | The step-5 commands were not re-run | **Closed at `b0c11c324`**, except the host full suite and the planted-defect mutations (see "Local commands run") |
| O9 | minor | Verification record | No change needed |

### Decision record

`docs/decisions/2026-09-28-openhands-resolver-isolation.md` records the resolver-mode narrowing and its amendment, **proposed 2026-09-28 and decided 2026-10-04: option 1 with trusted pre-push enforcement**.

The decision section records the following:
- the owner's delegation, in their words: "max quality sota convergenced resolution automation workflow at highest quality";
- the command center's proposal (option 1 with an in-CI tripwire);
- the GPT-family job 004 vote: disagree on sufficiency, because a check inside PR CI cannot protect against the commit under test. Each reason is re-checked against the GitHub, OpenSSF Scorecard and zizmor sources;
- the gate and its trusted-copy invariant;
- the separate defence-in-depth PR for workflow hardening;
- the residuals, the evidence, and the overturn to option 2 if the gate cannot be kept immutable to the agent.

Option 2's text stays as the alternative; only its line citations moved with the harness change.

The history below describes the amendment before the decision. After `cf42c6d08` and the wording fixes `f4e7aa2a2` and `54438ce7f`, the amendment's option 2:
- states that the resolver as built pushes to and opens PRs only in this repository;
- lists the separately reviewed harness change and fork ruleset it needs before a first run;
- marks the fork's holder undetermined: an organization the owner creates, or a second account. The choice changes the push identity, the fork's ruleset and the `--head <holder>:<branch>` value;
- sources the token and secret guarantee, for `pull_request` runs from a fork, in GitHub's "Workflows in forked repositories".

Both options now address condition 3 and CI egress. The 2026-10-03 fixture-repair section records native Git sources, the skipped-check alternative, the argument-normalization completeness finding, evidence limits and the native macOS condition that would overturn the repair. Lines 1-15 remain unchanged.

### What is not done, and what needs the owner

- **Owner decision: decided 2026-10-04.** It chose option 1 with trusted pre-push enforcement; the owner delegated the choice to converged practice. Still open:
  - workflow hardening, in a separate defence-in-depth PR: `permissions: {}` defaults, `persist-credentials: false`, cache, runner and timeout policy, a protected zizmor configuration, and a strict tripwire test on main. Until then, code under test runs with network, and that residual is accepted;
  - the first live run, which waits until this gate lands on main, its negative controls pass there and the stage gates are recorded;
  - the protected list, which is broad by design: all of `tests/**`, and every file a reachable step names. A resolver task that needs those files fails at the gate with no push;
  - option 2, an owner fork, which stays the overturn target and would need its own harness change.

  Resolver-PR check results are still not evidence of model-code safety.
- G4 remains unrecorded. Because no `stage-gates.json` exists, a real run today refuses earlier, at the gates stage, with `stage_gates_not_recorded`. Once the recorded stage gates and G5 pass, it refuses with `stage_gate_g4_not_recorded` until the coordinator records the isolated reviewer argv hash.
- G5 and `stage-gates.json` remain required. Both gateways' provider settings and the confinement design still need their own acceptance.
- These remain separate required steps:
  - fresh P0-P2 receipts, P3-P5 and G2 image qualification;
  - the reviewers' re-read of the step 6 delta;
  - the required contexts on the final head;
  - the first live draft-PR attempt.

  The native macOS check of the alias repair and the `TMPDIR` repair passed on `db24156b9`. A7's environment-name read at teardown stays in place.

### Host evidence

Not applicable: no file under `evidence/hosts/` changes. The historical macOS artifact is distinguished from a new native run, and no live resolver acceptance is claimed.

### Checklist

- [x] No GitHub Actions or workflow files changed; workflow hardening remains a separate defence-in-depth PR.
- [x] New Actions permissions or pins are not introduced by this change.
- [x] No credential value was read or published. Gitleaks scans of the history (36 commits at `b0c11c324`) and of both step 6 commits report no leaks, and the pre-push registry tests passed.
- [x] No new paid hosting, subscription or billing surface.
- [x] Peer-owned files and worktrees preserved.
- [x] Separate Stage 2 GPT review and whole-head Opus closure verdicts recorded, including residuals.
- [ ] The reviewers' re-read of the step 6 delta recorded.
- [ ] Every review thread resolved and all eight required contexts SUCCESS on the pushed head.
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