Skip to content

fix(agents): apply harness review findings - #149

Merged
furedea merged 14 commits into
mainfrom
fix/agent-harness-review-findings
Sep 28, 2026
Merged

furedea merged 14 commits into
mainfrom
fix/agent-harness-review-findings

Conversation

@furedea

@furedea furedea commented Sep 23, 2026 •

Copy link
Copy Markdown
Owner

Summary

Applies the agents/ harness review findings tracked in #148, apart from the items listed under Departures.

Hook adapters

  • Moved the logic shared by the Codex, Devin, pi, and Hermes adapters into agents/hooks/lib/provider_adapter.py. Each adapter now only declares a Profile.
  • Made the Devin adapter executable. It was mode 100644, so its hooks could not run.
  • Fixed where the adapters had drifted apart:
    • All four now pass normalized payloads.
    • The secret-path label is renamed to guard_secret_paths.sh.
    • Hermes no longer mutates its input.
    • Devin and Hermes log session-start compaction only for compact/resume sources.
    • pi compaction events are now recorded.
  • The Claude file guard and lint now cover NotebookEdit. The read guard covers Grep when its path is a single file.

Sandbox and permissions

  • Claude: every sandbox escape now needs the user's approval, even in auto mode.
    • allowUnsandboxedCommands stays true, because commit signing, push credentials, and the nix daemon cannot be reached from inside the sandbox.
    • Bash(dangerouslyDisableSandbox:true) is an ask rule. With it, auto mode can no longer escape the sandbox on the classifier's approval alone. The rule reaches the installed settings through fix: protect provider files and keep source Claude permission rules agent-harness#140, and flake.lock is bumped for it.
    • The sandbox write allowance used $HOME/.cache/nix, which Claude does not expand, so it never applied. It is now ~/.cache/nix, and ~/.cache/uv is added.
    • excludedCommands entries use globs (gh *, herdr *, docker *, watchman *). An entry without a glob matches only the bare command, so the old entries never applied.
    • herdr commands that drive other panes now ask first. The earlier send rules did not match send-text or send-keys, and agent prompt had no rule, so another agent's approval dialog could be answered without asking.
    • ~/.local/state/agent-harness (verification records) and the dprint cache are writable, so verify runs inside the sandbox.
    • lefthook moves to 2.x (nixpkgs-unstable). 1.13.6 always ran hook commands under a pty, and the sandbox denies that.
  • The secret read denials **/*key*, **/*token*, and .env* also matched source files and .envrc. That broke pytest and nix evaluation in the sandbox. They are now limited to credential-shaped names (*.key, *_token, *token*.json, *.pem, .env, .env.*, and so on).
  • docker build moves from allow to ask.
  • Codex:
    • Removed the unknown remote_connections feature.
    • Kept external_migration, because codex 0.155.1 still reads it.
    • Enabled multi_agent_v2 so its wait timeouts take effect.
  • Added git to the manifest runtime_commands.

Nix

  • git cc: codex exec --full-auto became -s read-only, because the flag was removed.
  • Removed the unguarded opencode package.
  • New contracts check both changes.

Skills

  • project-setup can now be loaded implicitly.
  • PRs are created with an explicit body.
  • adr's triggers are now in its description.
  • The Issue body, glossary, and completion rules each have a single owner.
  • Removed generic or drifted content from tsdd, nix-dotfiles, python-style, rust-style, bash-style, and github-actions-style.
  • verify_page.py now detects SVG image/use, embed, track, input type=image, and meta refresh loads.
  • test_workflow_skill_layout.py was tied to past changes. It now runs contract tests over every skill: frontmatter, links and anchors, and handoff targets.

Verification selection

  • If every changed source of a language is explicitly mapped to another language's tests, that language is no longer planned. For example, agent-harness hook_bridge.ts is embedded with include_str! and tested by Rust. Unmapped sources still require their own runner and fail closed.

Refs #148

Verification

  • verification_session.py verify: pytest 1495 passed (full suite), bats 13 passed.
  • nix build of the home activation package plus the home-configuration and host-configuration checks succeeded.
  • lefthook pre-commit passed on every commit.

Departures

  • The AGENTS.md function-size rule was reviewed with the user before it was changed, as a separate step. The 2-4 line and one-level nesting quotas become an intent-based extraction rule, with nesting kept to about two levels.
  • Protecting provider files other than Claude/Codex, and isolating the pi bridge interpreter, are handled in fix: protect provider files and keep source Claude permission rules agent-harness#140 (merged). This PR bumps flake.lock to it.
  • herdr-delegation was not split into references/. That item was optional.

🤖 Generated with Claude Code

furedea and others added 5 commits September 23, 2026 18:59
Move shared Codex, Devin, pi, and Hermes adapter logic into hooks/lib/provider_adapter.py.

Make the Devin adapter executable and pass normalized payloads from every provider.

Stop mutating Hermes input; log session-start compaction only for compact/resume.

Cover NotebookEdit in the file guard and lint, and single-file Grep in the read guard.

Refs #148

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Disallow unsandboxed commands in Claude; allow the uv cache and run gh outside the sandbox.

Narrow key and token read denials to credential-shaped names so source files stay readable.

Require approval for docker build, drop removed Codex features, and declare git as a runtime.

Refs #148

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
codex exec no longer accepts --full-auto; the alias only needs a read-only sandbox.

Remove opencode, which had no harness hooks or permissions, and check both contracts.

Refs #148

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

Flag srcset only for non-data candidates and meta refresh or image inputs only when they load.

Refs #148

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Let models load project-setup, which other skills name as the setup owner.

Create PRs with an explicit body and move adr triggers into its description.

Give Issue body, glossary, and completion rules a single owning skill.

Drop generic or drifted content; align python, rust, bash, and actions guidance with tools.

Replace history-bound skill layout tests with contracts over every skill.

Refs #148

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings September 23, 2026 10:01

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

… rule

The 2-4 line and one-level nesting quotas contradicted the intent-based extraction rule.

They pushed agents toward over-splitting functions.

Refs #148

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
furedea and others added 6 commits September 23, 2026 20:37
Embedded assets such as TypeScript bridge files tested by Rust no longer demand a JS runner.

Unmapped sources of that language still require their own runner and fail closed.

Refs #148

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Keep parse-once boundaries, failures as values, exhaustive sum types, and the limit of types.

These were lost when the tsdd Type-Driven Design section was removed.

Refs #148

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
external_migration is still read by codex 0.155.1, so restore it instead of dropping it.

Enable multi_agent_v2 so its wait timeouts take effect; remote_connections stays removed.

Refs #148

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude does not expand $HOME in sandbox paths, so the nix and uv cache allowances never applied.

Run herdr outside the sandbox like gh, since its socket is denied and escaping is now disabled.

Refs #148

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Allow the verification state directory and dprint cache so verify runs without escaping.

Ask before herdr commands that drive other panes; send-text, send-keys, and prompt were not covered.

Refs #148

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
lefthook 1.13.6 always ran hook commands under a pty, which the Claude and Codex sandboxes deny.

Take lefthook from nixpkgs-unstable in home and the devShell, and check the minimum version.

Refs #148

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
furedea added a commit to furedea/agent-harness that referenced this pull request Sep 28, 2026
…140)

Closes #139. Refs furedea/dotfiles#148 (review of furedea/dotfiles#149).

## Summary

- **Protected paths are derived, not hand-listed.**
`protected_paths_for_runtime` now builds the policy from the same tables
the installer uses: `InstalledLayout::synced_directories`
(Claude/Codex/Devin/Hermes/pi hooks and the Claude statusline;
`render_installation` copies through the same table),
`bridges::hermes_plugin_files`, external hook bundle assets, and
`InstalledLayout::generated_files` (all managed files plus the generated
Claude rule files and the `~/.hermes/config.yaml` sync target). The
hand-maintained `static_protected_paths` is removed.
`protected_paths.json`, Claude `Edit(...)` deny,
`sandbox.filesystem.denyWrite`, and the Codex guarded filesystem all
consume this one list.
- Newly protected: `~/.devin/hooks/**`, `~/.pi/hooks/**`,
`~/.hermes/hooks/**`, `~/.pi/agent/hooks.json`,
`~/.pi/agent/extensions/hook_bridge.ts`, `~/.hermes/hooks.json`,
`~/.hermes/plugins/agent-harness-hooks/*`, `~/.claude/statusline/**`,
`~/.codex/config.toml`, `~/.config/devin/config.json`,
`~/.hermes/config.yaml`.
- **pi bridge isolation.** `hook_bridge.ts` spawns `python3 -I -B
<adapter>`, matching the adapter shebang.
- **Source Claude permission rules are kept.**
`generate-claude-settings` and `install` dropped every `Bash(...)` entry
declared in the source `claude/settings.json`, so a Claude-only rule
such as `Bash(dangerouslyDisableSandbox:true)` in `permissions.ask`
never reached the generated or synced `~/.claude/settings.json`.
Generated settings now keep every source `allow` / `ask` / `deny` entry
followed by the generated entries. Each rule is listed once, and a rule
in several lists stays only in the most restrictive one (`deny` > `ask`
> `allow`), so the merge never widens a permission. `defaultMode` and
other permission keys pass through; non-string entries and non-array
lists are rejected. ADR-0018 records the decision.

## Notes

- Protection stays per-file because the runtime guard compares exact
paths. Every file under the source `hooks/` tree, including nested
modules such as `hooks/lib/provider_adapter.py`, maps into
`~/.claude/hooks/` and is protected; hook runtime logs under
`docs/logs/` remain excluded as before.
- Skills are rendered but intentionally not protected; they carry no
hook or permission settings.
- `sync-claude-settings` still replaces the whole `permissions` key, so
interactively saved approvals in `~/.claude/settings.json` do not
survive a sync; persistent rules belong in the source settings or
`command_permissions.json`.

## Tests

-
`complete_source_protects_every_rendered_provider_file_and_sync_target`
walks an installed prefix and asserts every rendered file (except
skills) plus each mutable sync target is in `protected_paths.json`,
Claude `Edit(...)` deny, and the Codex guarded filesystem. The fixture
gains `hooks/lib/provider_adapter.py` and
`claude/statusline/statusline.py`.
- `pi_bridge_runs_the_adapter_with_an_isolated_interpreter`.
-
`complete_source_keeps_source_permission_rules_in_generated_claude_settings`
and `installed_claude_settings_keep_source_ask_rules_after_sync` cover
generate and install-then-sync; `claude_permissions` unit tests cover
dedupe and restrictive precedence.
- Local: `cargo fmt --check`, `cargo clippy --all-targets -- -D
warnings`, `cargo test`, `nix flake check`, Codex execpolicy bats — all
pass.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
furedea and others added 2 commits September 28, 2026 16:51
…readable

Glob-less excludedCommands entries match only the bare command, so gh and herdr stayed sandboxed.

The .env* read denial also matched .envrc, which broke nix evaluation in the sandbox.

Refs #148

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Re-enable unsandboxed retries for commit, push, and nix, which cannot run in the sandbox.

Add an ask rule for dangerouslyDisableSandbox so auto mode never escapes without the user.

Bump agent-harness so source permission rules survive generation (furedea/agent-harness#140).

Refs #148

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@furedea
furedea merged commit 7d33014 into main Sep 28, 2026
11 checks passed
@furedea
furedea deleted the fix/agent-harness-review-findings branch September 28, 2026 13:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants