Bug
The continueagent and devin agent adapters delegate GetAgentHooks to the claude-code adapter (writing hooks into .claude/settings.local.json), but neither implements UninstallHooks or AreHooksInstalled. The grok adapter uses the same delegation pattern and correctly implements both methods. Without them, installed hooks are never cleaned up — they remain in the workspace as orphaned AO hook commands that fire after AO stops managing the session.
Analyzed against: 96d1649 (current main)
Confidence: High — compared continueagent, devin, and grok adapters side-by-side.
Root Cause
continueagent (backend/internal/adapters/agent/continueagent/continueagent.go):
- Has
GetAgentHooks at line ~117, delegating to claudecode.Plugin{}
- Missing
UninstallHooks and AreHooksInstalled
devin (backend/internal/adapters/agent/devin/devin.go):
- Has
GetAgentHooks at line ~125, delegating to claudecode.Plugin{}
- Missing
UninstallHooks and AreHooksInstalled
grok (correct reference implementation, backend/internal/adapters/agent/grok/grok.go):
- Has
GetAgentHooks at line ~109
- Has
UninstallHooks at line ~119 — delegates to claudecode.Plugin{}
- Has
AreHooksInstalled at line ~127 — delegates to claudecode.Plugin{}
The framework invokes UninstallHooks and AreHooksInstalled via type assertion on the agent adapter. When the assertion fails (for continueagent/devin), the call is silently skipped.
Reproduction
- Start a session with the
continueagent or devin adapter
- AO installs hooks in
.claude/settings.local.json via GetAgentHooks
- Kill or cleanup the session
- Check
.claude/settings.local.json in the workspace — AO hooks persist
- If the workspace is reused (e.g., session restore or new session on same branch), the orphaned hooks fire
ao hooks commands that may interfere with the new session
Impact
- Orphaned hooks in workspace after session cleanup
- Hooks fire for the wrong session context on workspace reuse
- Only affects
continueagent and devin adapters
Suggested Fix
Add UninstallHooks and AreHooksInstalled to both adapters, delegating to claudecode.Plugin{} just like grok does:
func (p *Plugin) UninstallHooks(ctx context.Context, inv ports.AgentInvocation) error {
return (&claudecode.Plugin{}).UninstallHooks(ctx, inv)
}
func (p *Plugin) AreHooksInstalled(ctx context.Context, inv ports.AgentInvocation) (bool, error) {
return (&claudecode.Plugin{}).AreHooksInstalled(ctx, inv)
}
Bug
The
continueagentanddevinagent adapters delegateGetAgentHooksto the claude-code adapter (writing hooks into.claude/settings.local.json), but neither implementsUninstallHooksorAreHooksInstalled. Thegrokadapter uses the same delegation pattern and correctly implements both methods. Without them, installed hooks are never cleaned up — they remain in the workspace as orphaned AO hook commands that fire after AO stops managing the session.Analyzed against:
96d1649(currentmain)Confidence: High — compared
continueagent,devin, andgrokadapters side-by-side.Root Cause
continueagent(backend/internal/adapters/agent/continueagent/continueagent.go):GetAgentHooksat line ~117, delegating toclaudecode.Plugin{}UninstallHooksandAreHooksInstalleddevin(backend/internal/adapters/agent/devin/devin.go):GetAgentHooksat line ~125, delegating toclaudecode.Plugin{}UninstallHooksandAreHooksInstalledgrok(correct reference implementation,backend/internal/adapters/agent/grok/grok.go):GetAgentHooksat line ~109UninstallHooksat line ~119 — delegates toclaudecode.Plugin{}AreHooksInstalledat line ~127 — delegates toclaudecode.Plugin{}The framework invokes
UninstallHooksandAreHooksInstalledvia type assertion on the agent adapter. When the assertion fails (for continueagent/devin), the call is silently skipped.Reproduction
continueagentordevinadapter.claude/settings.local.jsonviaGetAgentHooks.claude/settings.local.jsonin the workspace — AO hooks persistao hookscommands that may interfere with the new sessionImpact
continueagentanddevinadaptersSuggested Fix
Add
UninstallHooksandAreHooksInstalledto both adapters, delegating toclaudecode.Plugin{}just likegrokdoes: