Skip to content

feat(sidecar,shell): project write policy, git in the sandbox (v0.5.1 HGW-W2) - #80

Closed
SaulBuilds wants to merge 7 commits into
mainfrom
hgw/w1-project-write-policy
Closed

SaulBuilds wants to merge 7 commits into
mainfrom
hgw/w1-project-write-policy

Conversation

@SaulBuilds

@SaulBuilds SaulBuilds commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

HGW Week 1, runtime, 2 of 5. Stack: #79 → this PR (base hgw/w1-taint-hic) → #81 → #82 → #83. Green on its own. Refs #75 (HGW-W2). Owner decision 2026-10-15 (HGW).

What changes

Project roots and the file tools (agent-sidecar)

  • POST /sessions takes projectRoots: [abs path]. Each must be an existing folder under live read and write subtree folder grants, and it needs grants. A bad root refuses the session. project_create (feat(sidecar): project_create, HIC-approved (v0.5.1 HGW-W3, runtime half) #81) adds the folders it creates. SessionGrants keeps the list.
  • Inside a project root the file tools (fs_*, file_write, sheet_write) may create and change build configuration: package.json, foundry.toml, remappings.txt, medusa.json, Makefile, .cargo/config*, and so on. Outside a project root it is refused as before; the message now points to projects.
  • A marker file alone never makes a folder a project. The authority is core's projectRoots plus this session's project_create. A cloned repo could carry a marker.
  • Env files stay the member's everywhere. The file tools never write inside .git/; git does that through the sandboxed shell_run. This closes an existing gap: before, fs_write .git/hooks/pre-commit was allowed in any granted folder.
  • The toolchain's own config check (ffi, fs_permissions, solc path) is unchanged: forge still refuses risky config, whoever wrote it.

Sandbox (agent-shell)

  • Seatbelt: .git/ is now writable inside write roots, so git init, git config and git commit work. Still denied:
    • writing any hook file other than git's own *.sample files;
    • removing or moving .git/hooks;
    • putting a symlink at .git/hooks.
  • bwrap: .git/config is no longer masked, to match macOS. An existing .git/hooks stays read-only.
  • .env.example and .env.sample are readable ([v0.5.1 HGW-W2] Project-created write policy (build config, .git, .env templates, search path) #75). .env and .env.local stay unreadable and unwritable.
  • RunReport.git_warnings lists git state a run leaves behind that would make the member's own git run a program later, outside the sandbox. It covers hooks, core.hooksPath, core.fsmonitor, ! aliases, filters and drivers, and includes. It reports only what is new since before the run. shell_run puts it in the run facts and the summary.

Interface for the core lane

Needs a decision

Tests (red first)

  • project_policy_tests: +6. Build config is writable inside a project and nowhere else; env files are always refused; a marker alone is not a project; no .git writes; projectRoots is validated.
  • agent-shell tests/sandbox.rs, real Seatbelt: +4. git init/add/commit work; hooks stay closed against write, rename, copy, rm and symlink; env example files are readable and real env files are not; risky git state is reported.
  • agent-shell Linux bwrap run tests: +3. They are compiled on Linux only and skip without a working bwrap unless CITRATE_REQUIRE_BWRAP=1, which the Linux CI lane should set.
  • git_state unit: +2.
  • shell_run git warning: +1.
  • Workspace: 2255 → 2268 passed on macOS.

🤖 Generated with Claude Code

https://claude.ai/code/session_018wWZZ8GRVU9USh3kKQHsFe

SaulBuilds and others added 4 commits October 15, 2026 11:34
Owner decision 2026-10-15 (HGW (2)). Before this change any shell_run
output, even an approved `node -v`, tainted the session, and every later
command and sidecar-hosted write was declined for the rest of the
conversation ("which this tool's host cannot ask for"), with no way to
clear it.

Now, in a session opened with hicAware: true:
- a tainted effectful sidecar call (fs_*, file_write, sheet_write,
  toolchain tools, capsules) waits on a new per-session action card
  (GET /sessions/:id/actions/pending, POST .../actions/decide
  {id, allow, subject}); the card shows the tool, the exact canonical
  arguments, a summary, the hicReason and every taint source;
- shell_run keeps its own card and still asks after taint; the card
  now carries hicReason and taintSources;
- an approval releases that one call; the session stays tainted, so
  the next action asks again (never global, never automatic);
- decisions are recorded (kind sidecar.tool_call, write-ahead).

Without hicAware (headless, scheduled) nobody can be asked: the call is
declined with citrate_agent_loop::cannot_ask_reason, which names the
taint source and says why.

Tests: agent-loop taint_tests (+1), action_approvals unit (+3),
action_approvals_tests session/route tests (+6), shell_run_tests (+2,
one rewritten), files_tests taint case rewritten to the card flow.
Workspace: 2241 -> 2253 passed, 0 failed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018wWZZ8GRVU9USh3kKQHsFe
…t-retry (HGW-1)

Aligns the HGW-1 interface with the HGW-W1 issue (#74):
- routes are GET /sessions/:id/approvals/pending and
  POST /sessions/:id/approvals/:aid/decide {allow, subject};
- POST /sessions/:id/taint/clear {note}: the member clears the taint
  from an explicit card (TaintState::clear_by_member). Recorded
  write-ahead (kind taint.clear); with no records log, or when the
  record cannot be written, nothing is cleared (503). Scheduled
  (unattended) runs are never cleared (409); empty note 400; not
  tainted 409. Approving one action still never clears the taint;
- a declined card tells the model to ask the member instead of
  retrying (shell_run and approval cards).
Test support opens one undo store per manager (a store is locked while
open).

Tests: action_approvals_tests (+2). Workspace: 2253 -> 2255 passed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018wWZZ8GRVU9USh3kKQHsFe
…(HGW-2)

Owner decision 2026-10-15 (HGW). Hermes could not set up a project: the
file tools refused package.json, foundry.toml, remappings.txt and the
rest of the build configuration everywhere, and the macOS sandbox denied
every write to .git/config and .git/hooks, so `git init` failed even
offline.

Project roots (agent-sidecar):
- POST /sessions takes `projectRoots` (absolute, existing folders under
  live read+write subtree folder grants; needs `grants`). SessionGrants
  keeps them; project_create (next PR) adds the projects it makes.
- Inside a project root the file tools (fs_*, file_write, sheet_write)
  may create and change build configuration. Outside, refused as before
  (the message now points at projects). A marker file alone never makes
  a folder a project.
- Env files stay the member's everywhere. The file tools never write
  inside a .git folder (git does, through the sandboxed shell_run).
- The toolchain's own config check (ffi, fs_permissions, ...) is
  unchanged: forge still refuses risky config whoever wrote it.

Sandbox (agent-shell):
- Seatbelt: .git/ is writable inside write roots (git init/config/commit
  work); a hook file other than git's *.sample files is still denied, as
  are removing or moving .git/hooks and symlinking a new one in.
- bwrap: .git/config is no longer masked (parity); existing .git/hooks
  stays read-only.
- RunReport.git_warnings: risky git state a run leaves behind that would
  run a program outside the sandbox later (hooks, core.hooksPath,
  core.fsmonitor, `!` aliases, filters, includes, ...), new since before
  the run. shell_run puts it in the run facts and the summary.
- Linux bwrap run tests (skip unless bwrap works; CITRATE_REQUIRE_BWRAP=1
  makes them fail instead, for the Linux CI lane).

Tests: project_policy_tests (+6), agent-shell sandbox (+3 macOS real
Seatbelt incl. git init/commit and hooks closed; +3 Linux), git_state
unit (+2), shell_run git warning (+1). Workspace: 2253 -> 2265 passed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018wWZZ8GRVU9USh3kKQHsFe
…x (HGW-2, #75)

Per the HGW-W2 issue (#75): the Seatbelt `.env*` deny and the bwrap
`.env*` masks exempt the templates' `.env.example` and `.env.sample`
(not secrets; agent-guard exempts them the same way). `.env` and
`.env.local` stay neither readable nor writable.

Tests: sandbox env_files unit extended; real-Seatbelt test (+1).
Workspace: 2267 -> 2268 passed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018wWZZ8GRVU9USh3kKQHsFe

@BerryManifold BerryManifold left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Holding this permission expansion pending completion and verification of the git-state safeguards in the follow-up stack. Review found a blocking security boundary issue that remains present at #84. Details and local reproduction are retained privately under the organization security policy. Six project-policy tests passed locally; those tests do not cover the blocking scenario. Please address the follow-up before merging the affected stack.

SaulBuilds and others added 2 commits October 15, 2026 15:45
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018wWZZ8GRVU9USh3kKQHsFe
…dependency updates, #85)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018wWZZ8GRVU9USh3kKQHsFe
Base automatically changed from hgw/w1-taint-hic to main October 15, 2026 23:12
BerryManifold
BerryManifold previously approved these changes Oct 16, 2026

@BerryManifold BerryManifold left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approval submitted at the owner’s explicit direction. This does not retract or resolve the previously reported git-state cleanup finding; no new verification of a fix is claimed.

Matr0xshka
Matr0xshka previously approved these changes Oct 16, 2026

@Matr0xshka Matr0xshka 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.

Approval submitted at the owner’s explicit direction. This does not retract or resolve the previously reported git-state cleanup finding; no new verification of a fix is claimed.

The squash of #79 on main is identical to hgw/w1-taint-hic@00264e5, which this branch already contains, so the resolution keeps this branch's tree.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018wWZZ8GRVU9USh3kKQHsFe
@SaulBuilds
SaulBuilds dismissed stale reviews from Matr0xshka and BerryManifold October 16, 2026 03:49

The merge-base changed after approval.

SaulBuilds added a commit that referenced this pull request Oct 16, 2026
main is the squash of #79, identical to hgw/w1-taint-hic@00264e5, which this branch already contains; the resolution keeps this branch's tree. Lands #80-#84 as one squash (owner decision 2026-10-15).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018wWZZ8GRVU9USh3kKQHsFe
SaulBuilds added a commit that referenced this pull request Oct 16, 2026
… project_create, 24-step turns, auto-approve, git safety) (#84)

* feat(sidecar): untrusted-content taint escalates to HIC approval (HGW-1)

Owner decision 2026-10-15 (HGW (2)). Before this change any shell_run
output, even an approved `node -v`, tainted the session, and every later
command and sidecar-hosted write was declined for the rest of the
conversation ("which this tool's host cannot ask for"), with no way to
clear it.

Now, in a session opened with hicAware: true:
- a tainted effectful sidecar call (fs_*, file_write, sheet_write,
  toolchain tools, capsules) waits on a new per-session action card
  (GET /sessions/:id/actions/pending, POST .../actions/decide
  {id, allow, subject}); the card shows the tool, the exact canonical
  arguments, a summary, the hicReason and every taint source;
- shell_run keeps its own card and still asks after taint; the card
  now carries hicReason and taintSources;
- an approval releases that one call; the session stays tainted, so
  the next action asks again (never global, never automatic);
- decisions are recorded (kind sidecar.tool_call, write-ahead).

Without hicAware (headless, scheduled) nobody can be asked: the call is
declined with citrate_agent_loop::cannot_ask_reason, which names the
taint source and says why.

Tests: agent-loop taint_tests (+1), action_approvals unit (+3),
action_approvals_tests session/route tests (+6), shell_run_tests (+2,
one rewritten), files_tests taint case rewritten to the card flow.
Workspace: 2241 -> 2253 passed, 0 failed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018wWZZ8GRVU9USh3kKQHsFe

* feat(sidecar): approvals routes per #74, explicit taint clear, ask-not-retry (HGW-1)

Aligns the HGW-1 interface with the HGW-W1 issue (#74):
- routes are GET /sessions/:id/approvals/pending and
  POST /sessions/:id/approvals/:aid/decide {allow, subject};
- POST /sessions/:id/taint/clear {note}: the member clears the taint
  from an explicit card (TaintState::clear_by_member). Recorded
  write-ahead (kind taint.clear); with no records log, or when the
  record cannot be written, nothing is cleared (503). Scheduled
  (unattended) runs are never cleared (409); empty note 400; not
  tainted 409. Approving one action still never clears the taint;
- a declined card tells the model to ask the member instead of
  retrying (shell_run and approval cards).
Test support opens one undo store per manager (a store is locked while
open).

Tests: action_approvals_tests (+2). Workspace: 2253 -> 2255 passed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018wWZZ8GRVU9USh3kKQHsFe

* feat(sidecar,shell): project write policy and git inside the sandbox (HGW-2)

Owner decision 2026-10-15 (HGW). Hermes could not set up a project: the
file tools refused package.json, foundry.toml, remappings.txt and the
rest of the build configuration everywhere, and the macOS sandbox denied
every write to .git/config and .git/hooks, so `git init` failed even
offline.

Project roots (agent-sidecar):
- POST /sessions takes `projectRoots` (absolute, existing folders under
  live read+write subtree folder grants; needs `grants`). SessionGrants
  keeps them; project_create (next PR) adds the projects it makes.
- Inside a project root the file tools (fs_*, file_write, sheet_write)
  may create and change build configuration. Outside, refused as before
  (the message now points at projects). A marker file alone never makes
  a folder a project.
- Env files stay the member's everywhere. The file tools never write
  inside a .git folder (git does, through the sandboxed shell_run).
- The toolchain's own config check (ffi, fs_permissions, ...) is
  unchanged: forge still refuses risky config whoever wrote it.

Sandbox (agent-shell):
- Seatbelt: .git/ is writable inside write roots (git init/config/commit
  work); a hook file other than git's *.sample files is still denied, as
  are removing or moving .git/hooks and symlinking a new one in.
- bwrap: .git/config is no longer masked (parity); existing .git/hooks
  stays read-only.
- RunReport.git_warnings: risky git state a run leaves behind that would
  run a program outside the sandbox later (hooks, core.hooksPath,
  core.fsmonitor, `!` aliases, filters, includes, ...), new since before
  the run. shell_run puts it in the run facts and the summary.
- Linux bwrap run tests (skip unless bwrap works; CITRATE_REQUIRE_BWRAP=1
  makes them fail instead, for the Linux CI lane).

Tests: project_policy_tests (+6), agent-shell sandbox (+3 macOS real
Seatbelt incl. git init/commit and hooks closed; +3 Linux), git_state
unit (+2), shell_run git warning (+1). Workspace: 2253 -> 2265 passed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018wWZZ8GRVU9USh3kKQHsFe

* feat(sidecar): project_create tool, HIC-approved (HGW-3)

Hermes had no way to start a project: there was no default folder and
no tool to make one, and template rendering was a panel action only.

project_create {name, template?, params?} (sidecar-hosted, offered to
sessions with folder grants when CITRATE_HERMES_PROJECTS=1):
- name is one plain folder name (no separators, no `..`, no leading
  `.`/`-`/`~`, at most 64 chars); refused before anything is asked;
- the projects root (CITRATE_HERMES_PROJECTS_ROOT, passed by core;
  default ~/Citrate/Projects) must exist and be under live read+write
  subtree folder grants; <root>/<name> must not exist (never reused);
- always waits on an action card (HGW-1 queue) showing the exact folder
  and template; no HIC-aware app = nothing created; a tainted session
  shows one card carrying the taint reason;
- after approval (grants re-checked): creates the folder, renders the
  template if asked, writes .citrate/project.json, and makes the folder
  a session project root (HGW-2 build-config writes apply); a failed
  render removes the new folder and says why; returns the path.

Template rendering stays core's: the TemplateRenderer trait is the seam,
and CliTemplateRenderer runs core's `citrate-templates render --root
<templates> --template <id> --tier <T> --out <dest> [--param k=v]...`
(CITRATE_HERMES_TEMPLATE_RENDERER + CITRATE_HERMES_TEMPLATES_ROOT,
CITRATE_HERMES_TEMPLATE_TIER) through the shell runner, in the OS
sandbox when there is one, writing only the new folder.

Tests: projects unit (+4), projects_tests session/route (+10, incl. the
CLI renderer under real Seatbelt). Workspace: 2265 -> 2279 passed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018wWZZ8GRVU9USh3kKQHsFe

* feat(shell): .env.example and .env.sample stay readable in the sandbox (HGW-2, #75)

Per the HGW-W2 issue (#75): the Seatbelt `.env*` deny and the bwrap
`.env*` masks exempt the templates' `.env.example` and `.env.sample`
(not secrets; agent-guard exempts them the same way). `.env` and
`.env.local` stay neither readable nor writable.

Tests: sandbox env_files unit extended; real-Seatbelt test (+1).
Workspace: 2267 -> 2268 passed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018wWZZ8GRVU9USh3kKQHsFe

* feat(sidecar): auto-approve list for sandboxed project commands + e2e (HGW-5)

Owner decision 2026-10-15 (HGW (3), D-16 as amended): sandboxed,
offline, project-scoped read and build commands run without a card.

- The list is data: agent-sidecar/policy/auto_approve.toml (compiled
  in; CITRATE_HERMES_AUTO_APPROVE=0 turns it off, an absolute path
  replaces it). Rules are argv templates (program, subcommands, flags,
  value flags with kinds, required flags, operand kind and count). The
  shipped list: ls, pwd, cat, head, tail, wc, node <file>, npm run
  <script>, npm test, npx tsc, forge build/test, cargo build/test/check
  --offline, git init/status/diff/add/commit -m.
- shell_run releases a match without a card only when the session is
  untainted, the OS sandbox is enforced, and the folder is inside a
  project root; that run may write only the project (not the wider
  grant). Network-capable or unlisted commands, or anything outside a
  project, still ask. After taint every command asks (HGW-1).
- Each auto run is recorded write-ahead (kind shell.run.auto, actor
  agent, HIC-2); if it cannot be recorded it asks instead. Its result
  says so (run.approval = {mode: auto, rule, project}); member-approved
  runs say {mode: member}.
- agent-loop: ToolOutcome::Vouched. An auto run's output derives from
  the project's own files (no network, project-only writes, untainted
  session), like a folder-grant file read, so it does not taint; the
  worker wire maps it to Ok (fail closed across the process boundary).
- git through shell_run commits as "Hermes <hermes@citrate.invalid>"
  (the scratch HOME has no identity; git refused to commit).
- shell_run's description now states the approval semantics and says
  to ask, not retry, after a decline.

End to end (real Seatbelt, scratch home, scripted model): create the
project (one card) -> write package.json, build.js and a buggy source ->
git init/add/commit -> npm run build fails -> the model reads
"ReferenceError: greeet is not defined" from the tool result, fixes it
with fs_edit -> build passes -> commit; then untrusted output makes the
next listed command ask, with the taint reason on its card. Negative
checks in the same run: no write outside the project, no network, no
risky git state. Linux (bwrap) runs the same tests; they skip without
a working bwrap unless CITRATE_REQUIRE_BWRAP=1 (the CI lane).

Tests: auto_approve unit (+4), auto_approve_tests (+7), e2e (+1),
taint_tests vouched (+1), shell_run git identity (+1). Workspace:
2285 -> 2299 passed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018wWZZ8GRVU9USh3kKQHsFe

* docs(sidecar): project_create names the approvals routes (HGW-3)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018wWZZ8GRVU9USh3kKQHsFe

* feat(sidecar,shell): longer turns and failure output the model can act on (HGW-4)

A build needs more than 8 model calls, and a failing step has to show
the model why it failed.

- Turns: default maxSteps 8 -> 24 (sessions::DEFAULT_MAX_STEPS), cap
  stays 32. A session's maxSteps still wins; the sidecar-wide default
  is configurable with CITRATE_HERMES_MAX_STEPS (clamped 1..=32).
- Capture (agent-shell): a capped stream now keeps its first half-cap
  and its last half-cap (first error and final cause), with the
  truncation marker between them, instead of only the head.
- shell_run: a non-zero exit puts the exit status and the end of stderr
  (or stdout when stderr is empty), at most FAILURE_TAIL_BYTES = 2048,
  in the summary the model reads first, and as stderr_tail/stdout_tail
  in the run facts. Successful runs are unchanged.
- Toolchain: a run with no parseable report now states the exit code.
  Recognized compiler errors stay sanitized and trusted (the S6.3
  guarantee: injected text in sources never reaches trusted output).
  When no cause is recognized, the end of stderr is handed over raw,
  bounded, and the result is Untrusted, so it taints the session (and,
  with HGW-1, later actions go to HIC cards).

Tests: agent-shell runner (+1, two capture assertions extended),
loop_capability_tests (+3), shell_run failure tail (+1), toolchain
unparsed stderr (+1). Workspace: 2279 -> 2285 passed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018wWZZ8GRVU9USh3kKQHsFe

* feat(sidecar): narrow npm scripts, git hooks off, list route (HGW-5, #77)

Aligns HGW-5 with the HGW-W11 issue (#77):
- npm run is auto-approved only for the script names build, test,
  lint and typecheck (new rule field operand_values); any other script
  asks;
- every git run through shell_run gets command-scope config
  core.hooksPath=/dev/null and core.fsmonitor=false (outranks the
  repository's own config), so Hermes's git never runs hooks;
- GET /shell/auto-approve returns the list in effect (enabled,
  conditions, rules) for the app's Settings, behind the bearer.

Tests: matrix extended; route test (+1); git hooks/fsmonitor off (+1).
Workspace: 2302 -> 2304 passed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018wWZZ8GRVU9USh3kKQHsFe

* feat(shell): revert risky git state a run adds, report it (HGW-2, owner decision 2026-10-15)

After every run the runner reverts what the run added that would make git
start a program outside the sandbox later: risky config entries (hooksPath,
fsmonitor, sshCommand, pager/editor, aliases, filters, diff/merge drivers,
credential helpers, includes, non-https insteadOf, protocol allow=always,
gpg.program, ...), new hook files, and a hooks folder or config swapped for
a symbolic link. Normal config (user.*, remote.*, branch.*) stays; what was
there before the run is never touched. Each revert is a line in
RunReport.git_warnings and the shell_run summary says the changes were
undone.

A .git the run turned into a pointer (gitdir: file or symlink) is reported
with the risky state where it points, not changed: that folder may be the
member's own.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018wWZZ8GRVU9USh3kKQHsFe

* feat(sidecar): commits name the member and carry a Hermes trailer (HGW, owner decision 2026-10-15)

Author and committer of commits made through shell_run are the member's:
the identity the app passes (CITRATE_HERMES_GIT_USER_NAME/_EMAIL, both
set and valid), else the member's own global git config (read once with
the real git outside the sandbox, empty env but HOME), else
"Hermes <hermes@citrate.invalid>". They reach git as GIT_AUTHOR_*/
GIT_COMMITTER_*, which outrank every config file.

`git commit` gets --trailer "Co-authored-by: Hermes <hermes@citrate.invalid>",
and command-scope config keeps git adding it (trailer.ifmissing=add,
ifexists=addIfDifferent, commentChar #) over the repository's own.

Not the model's to change, so refused before the member is asked:
-c/--config-env for user.*, author.*, committer.*, trailer.*, include*,
core.commentChar/String, commit.cleanup; commit --author, --cleanup,
-C/-c/--reuse-message/--reedit-message (abbreviations included); and
git commit-tree. Trailer/cleanup/comment-char config a run writes into
the repository is reverted after it with the other risky git state.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018wWZZ8GRVU9USh3kKQHsFe

* feat(shell): git cleanup covers every repository in the write roots

The post-run git cleanup (HGW-2, owner decision 2026-10-15) now finds
repositories by walking every write root, before and after the run,
instead of checking only the run's folder, its parents and its direct
children:

- `.git` folders at any depth, bare repositories, linked worktrees
  (`.git/worktrees/*`) and submodule git folders (`.git/modules/**`);
- `config.worktree` is checked alongside `config`, and `hooks/` in each;
- repositories the run creates are reverted against an empty baseline;
- `.git` files/links and `commondir` are followed when they point at a
  git folder inside the write roots; ones pointing outside are reported
  and left unchanged;
- more keys that run a program or move the worktree are treated as risky
  (core.worktree, core.alternateRefsCommand, submodule.*.update = !...,
  hook.*, interactive.diffFilter, sendemail commands,
  gpg.ssh.defaultKeyCommand, tar.*.command, guitool.*.cmd).

The walk is bounded (300,000 entries, 48 levels). When it stops early
the report says so, and a repository the before-run walk did not reach
is reported rather than changed. Tests cover each case through the
runner, under Seatbelt on macOS and under bubblewrap on Linux.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018wWZZ8GRVU9USh3kKQHsFe

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@SaulBuilds

Copy link
Copy Markdown
Contributor Author

Landed in main via #84 (squash 75e21cd), per the owner's 2026-10-15 decision to land the HGW stack #80–#84 as one squash. Reviewed and approved here.

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.

3 participants