agent-orchestrator/backend/internal
Ashish Huddar 40bd2dfb2b
fix(sessions): stop AO hook files from making every worktree permanently dirty (#169)
* fix(sessions): stop AO hook files from making every worktree permanently dirty

Agent adapters write hook files (.codex/hooks.json, .opencode/plugins/
ao-activity.ts, .claude/settings.local.json, ...) into fresh session
worktrees as untracked files. `git worktree remove` (deliberately run
without --force) refuses on any untracked file, so Workspace.Destroy
failed for every session of the 12 workspace-writing harnesses:
POST /sessions/{id}/kill returned an unlogged 500 INTERNAL_ERROR and
`ao session cleanup` reported 'Would clean N' then '0 sessions cleaned'
with no reason, leaking workspaces forever.

Three coordinated fixes, none of which force-deletes user/agent work:

- Root cause: every adapter now writes a sentinel-guarded, self-ignoring
  .gitignore next to its hook files (hookutil.EnsureWorkspaceGitignore),
  so AO's own files no longer count as dirt while anything an agent
  drops — even in the same directory — still blocks teardown. A
  registry-wide conformance test enforces the contract for all current
  and future adapters. (Per-worktree .git/worktrees/<name>/info/exclude
  was evaluated first but git does not honor it.)
- Typed refusal: gitworktree.Destroy classifies a still-dirty refusal as
  ports.ErrWorkspaceDirty (git status probe). Kill maps it to success
  with freed=false (session terminated, worktree preserved); Cleanup
  reports it per-session as skipped-with-reason through the API
  (CleanupSessionsResponse.skipped), and the CLI prints
  'Skipped: <id> (workspace has uncommitted changes)' plus a summary.
- Observability: envelope.WriteError records the raw service error into
  a request-scoped slot and the access log attaches it to 5xx lines, so
  any remaining internal error is diagnosable server-side.

Worktrees created before this fix gain the .gitignore on restore (hook
install re-runs); their cleanup is otherwise reported as skipped instead
of erroring.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(cleanup): address Greptile P2s — surface dirty-probe failures, stop leaking raw errors

Two review findings on this PR:

- gitworktree.Destroy: when the isDirty probe itself failed, the error was
  silently discarded and the refusal looked identical to "registered but not
  dirty". The probe failure now rides the returned error (dirty probe: ...),
  so it reaches the access log via the 5xx error capture.

- Cleanup skip reasons: a non-dirty teardown failure put the raw error —
  including internal filesystem paths — into the public skipped[].reason
  field. The public reason is now the fixed string "workspace teardown
  failed"; the full cause goes to the daemon log (warn, with sessionID and
  path). The dirty-refusal reason is unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(gitworktree): wrap the dirty-probe error with %w per errorlint

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 11:06:45 +05:30
..
adapters fix(sessions): stop AO hook files from making every worktree permanently dirty (#169) 2026-06-11 11:06:45 +05:30
cdc feat: ao session claim-pr + spawn --claim-pr wiring (#101) 2026-06-06 00:01:03 +05:30
cli fix(sessions): stop AO hook files from making every worktree permanently dirty (#169) 2026-06-11 11:06:45 +05:30
config feat(frontend): scaffold for frontend with complimentary backend changes (#168) 2026-06-10 11:40:17 +05:30
daemon fix(codex): reliable activity signals — session-flag hooks, trust bypass, no_signal watchdog (#170) 2026-06-11 10:36:45 +05:30
daemonmeta fix(cli): verify daemon ownership before stop signal 2026-06-01 01:45:04 +05:30
domain fix(codex): reliable activity signals — session-flag hooks, trust bypass, no_signal watchdog (#170) 2026-06-11 10:36:45 +05:30
httpd fix(sessions): stop AO hook files from making every worktree permanently dirty (#169) 2026-06-11 11:06:45 +05:30
integration fix: 7 bugs from discussion #149 smoke walk (envelope, spawn, CDC, observer) (#153) 2026-06-07 07:35:46 +05:30
lifecycle fix(codex): reliable activity signals — session-flag hooks, trust bypass, no_signal watchdog (#170) 2026-06-11 10:36:45 +05:30
observe feat(tracker): ApplyTrackerFacts reducer + shared observer skeleton (#112) (#116) 2026-06-07 21:29:00 +05:30
ports fix(sessions): stop AO hook files from making every worktree permanently dirty (#169) 2026-06-11 11:06:45 +05:30
processalive refactor: simplify session lifecycle and zellij runtime (#62) 2026-06-01 08:42:49 +05:30
runfile refactor: simplify session lifecycle and zellij runtime (#62) 2026-06-01 08:42:49 +05:30
service fix(sessions): stop AO hook files from making every worktree permanently dirty (#169) 2026-06-11 11:06:45 +05:30
session_manager fix(sessions): stop AO hook files from making every worktree permanently dirty (#169) 2026-06-11 11:06:45 +05:30
storage/sqlite fix(codex): reliable activity signals — session-flag hooks, trust bypass, no_signal watchdog (#170) 2026-06-11 10:36:45 +05:30
terminal feat: ao session claim-pr + spawn --claim-pr wiring (#101) 2026-06-06 00:01:03 +05:30