Skip to content

fix(goose): wire lifecycle hooks, AGENTS.md context, session support - #13

Merged
rsnodgrass merged 1 commit into
mainfrom
ryan/goose-hooks-and-context-files
Jul 31, 2026
Merged

fix(goose): wire lifecycle hooks, AGENTS.md context, session support#13
rsnodgrass merged 1 commit into
mainfrom
ryan/goose-hooks-and-context-files

Conversation

@rsnodgrass

@rsnodgrass rsnodgrass commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

What broke

GooseAgent was written when Goose had no hooks and no addressable sessions. Both facts changed, and the agent definition didn't.

Goose shipped a full lifecycle hooks system in v1.38 (May 2026), built on the Open Plugins hooks specification. It also loads AGENTS.md as its primary context file — ahead of .goosehints — and puts a session_id on every hook payload.

Meanwhile agents/goose.go still declared:

  • Hooks: false — "CLI-based"
  • ContextFiles() == [".goose/config.yaml", ".goosehints"] — a config file listed as an instruction file, and AGENTS.md missing entirely
  • SupportsSession() == false
  • no LifecycleEventMapper — so "goose" was absent from BuildEventPhaseMap()

That last one is the load-bearing bug.

Why the missing mapper matters

BuildEventPhaseMap() keys on AGENT_ENV alias. With no Goose entry, a consumer resolving a Goose hook event had nothing to look up and fell through to scanning every other registered agent's map.

flowchart LR
    EV["Goose fires SessionStart"] --> LOOK{"alias goose in<br/>BuildEventPhaseMap"}
    LOOK -->|"before: absent"| SCAN["scan every other agent map<br/>until a string matches"]
    LOOK -->|"after: present"| DIRECT["PhaseStart"]
    SCAN --> LUCK["PhaseStart, by coincidence"]
Loading

It happened to produce the right answer, because Goose's event names are string-identical to Claude Code's and Droid's and all three map them to the same phases. That is a coincidence, not a contract — the first agent to map Stop or SessionEnd differently silently breaks Goose. This PR makes the mapping explicit.

What this PR ships

  • EventPhases() covering the six Goose events with a canonical phase equivalent:

    Goose event Phase
    SessionStart PhaseStart
    SessionEnd PhaseEnd
    PreToolUse PhaseBeforeTool
    PostToolUse PhaseAfterTool
    UserPromptSubmit PhasePrompt
    Stop PhaseStop
  • AgentENVAliases() == ["goose"]

  • ContextFiles() == ["AGENTS.md", ".goosehints"] — in Goose's own load order, so a caller injecting a context marker writes to the file Goose reads first

  • Capabilities(): Hooks: true, MinVersion: "1.38.0"

  • SupportsSession() == true, following the OpenCode precedent — SessionID(env) still returns empty, because Goose exposes no session-ID environment variable and callers must read it from the hook payload

Deliberate omissions

  • PhaseCompact is not mapped. Goose fires no compaction event. Mapping one would promise a re-prime that never happens — better to leave the gap visible than to paper over it. This is an upstream limitation no consumer can work around.
  • Goose's five extra eventsPostToolUseFailure, BeforeReadFile, AfterFileEdit, BeforeShellExecution, AfterShellExecution — have no canonical phase equivalent and are omitted rather than force-fit.
  • CustomCommands stays false. Goose has recipes and skills; neither is a slash command.

Stale test assertions refreshed

Three tests encoded the old beliefs and had to move, exactly as the Pi refresh did in 0.1.10:

Test Change
TestNonLifecycleAgents_DoNotImplementMapper Goose removed
TestAgentSessionSupport Goose moved from "non-session agents" to "hook-only agents support sessions but return empty SessionID"
allAgentSpecs() Goose entry contextFiles, capabilities, supportsSession, plus new isLifecycleAgent / envAliases / eventPhaseCount

New agents/goose_test.go adds five focused tests. Two are worth calling out because they pin intent, not current behavior:

  • TestGooseContextFiles asserts the order, not just membership — AGENTS.md must be index 0. A caller picking files[0] as its injection target depends on that.
  • TestGooseEventPhases asserts no phase in the map is PhaseCompact, with a comment explaining why adding one would be a silent regression rather than a feature.

Test Plan

  • make check (fmt + lint + test) — 1134 tests pass, 2 pre-existing skips, 0 lint issues
  • go test ./agents/... -run Goose — new file green
  • Verified against a live Goose install (~/.config/goose, ~/.local/share/goose/sessions/sessions.db) and the upstream docs at block/goose@main:documentation/docs/guides/context-engineering/hooks.md

Follow-up

Cut v0.1.12 after merge. sageox/ox will bump to it so its Goose adapter work doesn't need a local localEventPhases override — the same migration Gemini is still waiting on (Gemini and Codex remain non-lifecycle agents here).

Summary by CodeRabbit

  • New Features

    • Added support for Goose lifecycle hooks across supported event phases.
    • Enabled Goose session support.
    • Added recognition through the goose environment alias.
    • Updated project context files to use AGENTS.md and .goosehints.
    • Set the minimum supported Goose version to 1.38.0.
  • Documentation

    • Added changelog details covering Goose hooks, sessions, context files, and capabilities.

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 5ec1d8b2-07b3-4156-afb0-21695a38c13d

📥 Commits

Reviewing files that changed from the base of the PR and between 1b4411f and 1a2c391.

📒 Files selected for processing (5)
  • CHANGELOG.md
  • agents/agents_comprehensive_test.go
  • agents/goose.go
  • agents/goose_test.go
  • agents/session_test.go

📝 Walkthrough

Walkthrough

Goose now supports lifecycle hooks, ordered AGENTS.md and .goosehints context files, session handling, native event-phase mappings, the goose environment alias, and a minimum version of 1.38.0. Tests and changelog entries document these updates.

Changes

Goose lifecycle support

Layer / File(s) Summary
Context and capability contract
agents/goose.go, agents/agents_comprehensive_test.go
Goose loads AGENTS.md before .goosehints, enables hooks, declares project context, requires Goose 1.38.0, and supports sessions.
Lifecycle events and session validation
agents/goose.go, agents/goose_test.go, agents/agents_comprehensive_test.go, agents/session_test.go, CHANGELOG.md
Native Goose events map to lifecycle phases through the goose alias, session behavior is validated, and the 0.1.12 changelog records the updated support.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Suggested reviewers: port8080

Sequence Diagram(s)

sequenceDiagram
  participant HookPayload
  participant GooseAgent
  participant EventPhaseMap
  HookPayload->>GooseAgent: provide native lifecycle event
  GooseAgent->>EventPhaseMap: resolve event through goose alias
  EventPhaseMap-->>GooseAgent: return mapped agent phase
Loading
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ryan/goose-hooks-and-context-files

Comment @coderabbitai help to get the list of available commands.

@rsnodgrass
rsnodgrass marked this pull request as ready for review July 31, 2026 00:13
@rsnodgrass
rsnodgrass merged commit cacaae4 into main Jul 31, 2026
6 checks passed
@rsnodgrass
rsnodgrass deleted the ryan/goose-hooks-and-context-files branch July 31, 2026 00:13
Comment thread agents/goose.go
func (a *GooseAgent) Capabilities() agentx.Capabilities {
return agentx.Capabilities{
Hooks: false, // CLI-based
Hooks: true, // lifecycle hooks via plugins (goose >= 1.38)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Goose hook capability has no installation backend

Capabilities() now advertises Hooks: true, but the fully wired setup registry registers a bare GooseAgent and never assigns a HookManager. Callers that select Goose based on this capability receive nil from HookManager() and cannot install, remove, or validate the Goose plugin hook configuration. Implement and register a Goose hook manager for Goose's plugin hooks contract, or leave Hooks disabled until that operation is supported.

Artifacts

Goose lifecycle exported API validation script

  • A Go program imported agentx setup and exercised Goose registration, lifecycle mappings, session support, context metadata, and hook-manager availability; it demonstrates the unsupported Hooks capability contract.

Goose lifecycle behavior before PR 13

  • The same validation program compiled against HEAD^ and showed Goose had no hook capability, lifecycle mapper, alias registration, or session support before the PR.

Goose lifecycle behavior at PR 13 HEAD

  • The validation program at HEAD showed all six lifecycle events and metadata were registered but failed because the exported setup path leaves Goose HookManager nil, proving the advertised hook capability is unusable.

Repository test suite after Goose lifecycle validation

  • The executed `go test ./... -count=1` suite passed across all repository packages, showing existing tests do not cover the exported HookManager capability mismatch.

View artifacts

T-Rex Ran code and verified through T-Rex

Fix in Claude Code

@greptile-apps

greptile-apps Bot commented Jul 31, 2026

Copy link
Copy Markdown

Greptile Summary

This change adds Goose lifecycle event mappings, hook/session metadata, and updated context-file ordering. The exported registry correctly exposes Goose’s six mapped lifecycle events, but it now reports hook support without providing a hook manager capable of installing or managing Goose hook configuration. Consumers that rely on the capability flag cannot complete hook setup, so the change should not merge until that capability is backed by an implementation or is no longer advertised.

Confidence Score: 3/5

Not safe to merge until T-Rex findings are addressed.

The exported setup path reproducibly reports Goose hook support while returning a nil hook manager. Lifecycle event resolution, context metadata, and session metadata work as expected, but the unavailable hook-management operation breaks consumers that configure hooks from agent capabilities.

T-Rex reproduced 2 failing behaviors at runtime in agents/goose.go; the change needs fixes before it is safe to merge.

Files Needing Attention: agents/goose.go needs a concrete Goose hook manager or must stop advertising hook support; setup/setup.go must wire that manager into the registered Goose agent.

T-Rex T-Rex Logs

What T-Rex did

  • Generated and attached a proof for the posted P1 finding concerning Goose lifecycle API validation.
  • Compared Goose lifecycle behavior before PR 13 and at PR 13 HEAD using the lifecycle logs and the validation script.
  • Ran the repository test suite after Goose lifecycle validation to verify the changes.
  • Generated a second finding-proof for another P1 finding.
  • Documented general-contract-validation-proof showing that all six Goose events resolve through BuildEventPhaseMap, but HookManager remains nil after setup, preventing hook installation.

View all artifacts

T-Rex Ran code and verified through T-Rex

Comments Outside Diff (1)

  1. General comment

    P1 Goose advertises hook support but setup provides no hook manager

    • Bug
      • The fully-wired exported setup path registers agents.NewGooseAgent() without assigning a Goose HookManager. The HEAD executable check observed hooks=true, correct lifecycle registration, and manager_nil=true, then failed because no supported API can install or validate Goose hooks.
    • Cause
      • Capabilities().Hooks was enabled and Goose was made a lifecycle mapper, but no adapter exists for Goose’s .agents/plugins/<name>/hooks/hooks.json contract and setup does not inject a manager.
    • Fix
      • Either implement and wire a Goose HookManager that writes/removes/validates Goose plugin hooks/hooks.json configurations, or keep Capabilities().Hooks false until installation is actually supported. Add an exported-setup integration test asserting every Hooks-capable registered agent has a non-nil HookManager.

    T-Rex Ran code and verified through T-Rex

Fix All in Claude Code

Reviews (1): Last reviewed commit: "fix(goose): wire lifecycle hooks, AGENTS..." | Re-trigger Greptile

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.

1 participant