Skip to content

feat(goose): session adapter and Open Plugins hook installer - #739

Merged
rsnodgrass merged 5 commits into
mainfrom
ryan/ox-adapter-goose
Jul 31, 2026
Merged

feat(goose): session adapter and Open Plugins hook installer#739
rsnodgrass merged 5 commits into
mainfrom
ryan/ox-adapter-goose

Conversation

@rsnodgrass

@rsnodgrass rsnodgrass commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Stacked on #738 (README/docs truth pass + Goose prime). Review that one first — this branch contains its commit.

What this ships

Goose becomes a Silver-tier AI coworker: sessions record to the Ledger, and hooks fire ox agent prime automatically. Before this, Goose got an AGENTS.md marker and nothing else.

New cmd/ox-adapter-goose/ (~900 LOC + ~600 test), plus the ox-side wiring and release plumbing.

flowchart LR
    GS["Goose session starts"] --> HK["hooks.json fires<br/>ox agent hook SessionStart"]
    HK --> PR["ox agent prime<br/>team context in"]
    PR --> REC["find-session, then<br/>drain on every turn"]
    REC --> DB["sessions.db<br/>SQLite read-only"]
    DB --> LG["Ledger"]
Loading

Two places this beats the template it was modeled on

cmd/ox-adapter-opencode is the only other SQLite reader, so it was the starting point. Goose's schema supports better in two ways worth not copying:

  • Offset is MAX(messages.id), not COUNT(*). Goose's id is AUTOINCREMENT, so it is a real watermark. A count-based offset silently replays or skips rows the moment any message is deleted. TestMaxMessageID_UsesRowidNotCount demonstrates the divergence directly: after one delete, the count-based watermark replays an entry that already streamed; the rowid watermark replays nothing.
  • Repo correlation uses sessions.working_dir, an actual column, matched exactly then by subdirectory prefix. OpenCode picks "most recent session, no repo filter." TestResolveSessionID_PrefersWorkingDir seeds a newer session in a different repo to prove recency does not win.

The hook installer is a new shape for ox

Every existing ox adapter edits one settings file. Goose follows the Open Plugins spec, so hooks live in a plugin directory:

<scope>/.agents/plugins/sageox/
├── plugin.json        manifest — Goose ignores a plugin without it
└── hooks/hooks.json   event → command rules

Design decisions worth reviewing:

  • No shell script on disk. command points straight at ox agent hook <Event>. Verified no shim is needed: ox takes the event name from argv, and Goose's stdin fields session_id / tool_name / tool_input are exact matches for what agentx.HookInput decodes. Unmapped Goose fields survive in RawBytes.
  • OX_PROJECT_ROOT is baked into project-scope commands. Goose sends working_dir only on tool events, so SessionStart alone gives ox nothing to locate the repo with and it would walk up from an arbitrary cwd. config.ResolveProjectRootOverride() already honors the variable and findProjectRoot checks it first. Paths are single-quoted — Goose runs commands via sh -c, so a repo path with a space would otherwise split.
  • matcher is never emitted. Goose treats it as a regex, and a bare "*" is invalid, so Goose logs a warning and silently skips the whole rule. Omitting the field is the only unambiguous "match everything." Pinned by TestInstallHooks_NeverEmitsMatcher.
  • Uninstall never blind-deletes. .agents/plugins/ is a shared namespace. Uninstall strips only rules carrying the ox agent hook marker, and removes the directory only when plugin.json carries x-ox-managed. Two tests cover it: a foreign rule survives, and a foreign manifest stops the directory delete.
  • check-hooks requires the manifest. A hooks.json with no sibling plugin.json looks installed on disk but Goose never loads it, so the hooks never fire. Reporting "installed" there would hide a completely dead setup.

Hook event coverage

Goose fires eleven events. ox installs seven:

Installed Why
SessionStart SessionEnd Stop prime, finalize, drain
UserPromptSubmit whisper push — the primary channel
PreToolUse PostToolUse incremental drain
PostToolUseFailure not redundant — Goose fires PostToolUse only on success, so without this a turn ending in a failed tool call is invisible until the next success or Stop
Skipped Why
BeforeReadFile BeforeShellExecution strict subsets of PreToolUse — reading a file and running a shell command are both tool calls
AfterFileEdit AfterShellExecution strict subsets of PostToolUse (and success-only)

Installing the four would spawn ox agent hook twice per tool call for zero signal ox does not already have. TestInstallHooks_EventCoverage asserts both lists so the reasoning survives contact with the next reader. If you want literal 11-event coverage anyway, it is a four-line change to hookEvents — say so and I will flip it.

agentx

Two releases, both merged and tagged:

  • v0.1.12 (#13) — Goose was modeled as hookless and sessionless with the wrong context files. Now implements LifecycleEventMapper.
  • v0.1.13 (#14) — canonical HookEventPostToolUseFailure, mapped for Goose.

So no localEventPhases entry is needed in cmd/ox/agent_hook.go — unlike Gemini, which is still waiting on that migration.

Two blockers that are easy to miss

  • internal/session/adapters/adapter.go"goose": "generic""goose": "goose". Without this, lookupAdapter resolves the alias to the always-registered generic adapter, GetAdapter's RegisterExternalAdapters() retry never runs, and the binary is permanently shadowed.
  • internal/session/adapters/session_roots.gofail-closed. The daemon rejects every SessionFile from an adapter with no allow-listed root.

Drift backfilled while here

The release plumbing was already behind, and Goose would have widened the gap:

File Was Now
scripts/install.sh 5 of 8 adapters — pi, aider, droid never landed on PATH all 9
cmd/ox/upgrade.go missing droid complete
internal/adapter/registry.yaml no droid entry at all droid + goose

Test Plan

  • make lint0 issues
  • make test17151 tests pass, 1086 skipped, 0 failures
  • go test ./cmd/ox-adapter-goose/... — fixture sessions.db built in t.TempDir() covering all five content_json block types, malformed JSON, failed tool envelopes, and the offset semantics

Verified against a real Goose install on this machine, not just fixtures:

  • detect{"detected":true,"reason":"found sessions.db"}
  • find-sessiongoose:20260602_3, offset 104
  • read → 34 entries (1 user, 1 assistant, 32 tool), model gemma4:26b extracted from model_config_json
  • read-from-offset 100 → 4 entries, new_offset: 104; read-from-offset 104 → empty. The one-shot path works — that is exactly the path broken for OpenCode today (ox-8arr)
  • install-hooks → correct plugin directory, quoted paths, no matcher, 30s on SessionStart and 10s elsewhere; check-hooks confirms

Not yet verified: whether Goose injects, warns on, or ignores hook stdout. handleStart pipes ox agent prime JSON to stdout. This does not block recording or priming, but it is why I would watch the first real Goose session before assuming UserPromptSubmit whispers render cleanly.

Known limitation, stated not hidden

Goose has no compaction event. Team context primed at session start is lost when Goose compacts, and ox cannot work around it. Documented in the compatibility guide, the CHANGELOG, and on EventPhases() upstream so nobody "fixes" it by mapping a phase that never fires.

Co-Authored-By: SageOx

Summary by CodeRabbit

  • New Features

    • Added full Goose integration, including session discovery, transcript import, incremental updates, prior-session capture, and repository-aware correlation.
    • Added Goose lifecycle-hook installation, inspection, diagnostics, and safe removal.
    • Goose is now included in supported installation, upgrade, and release workflows.
    • Added read-only ox recap evidence reports in text and JSON formats.
    • Added Goose detection and compatibility support across agent setup and diagnostics.
  • Documentation

    • Expanded compatibility guides with Goose and additional supported AI coding tools.
    • Documented integration methods, session recording, hook behavior, and status reporting.
  • Bug Fixes

    • Improved OpenCode session reading reliability.

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Added a bundled Goose adapter with SQLite transcript ingestion, incremental serving, Open Plugins lifecycle hooks, diagnostics, validation, detection, packaging, agent registration, and expanded compatibility documentation.

Changes

Goose support

Layer / File(s) Summary
SQLite session ingestion
cmd/ox-adapter-goose/session.go, cmd/ox-adapter-goose/serve.go, cmd/ox-adapter-goose/session_test.go
Reads Goose sessions from SQLite, parses content and tool events, extracts metadata, and supports incremental, prior-session, and serve-mode flows.
Open Plugins hook integration
cmd/ox-adapter-goose/hooks.go, cmd/ox-adapter-goose/hooks_test.go, cmd/ox-adapter-goose/diagnose_test.go
Installs, checks, diagnoses, and uninstalls owned lifecycle hooks across project and user scopes while preserving unrelated rules and rejecting unsafe paths.
Adapter runtime and validation
cmd/ox-adapter-goose/main.go, cmd/ox-adapter-goose/main_test.go, cmd/ox/hook_content_validator*
Wires detection, diagnostics, imports, capabilities, path resolution, offset-based serving, and Open Plugins content validation into the runtime.
Platform and adapter registration
internal/adapter/*, internal/prime/*, internal/session/adapters/*, cmd/ox/doctor_*.go, cmd/ox/instruction_files*, go.mod, cmd/ox-adapter-opencode/*
Registers Goose capabilities, aliases, session roots, agent classification, instruction-file behavior, bundled metadata, and related SQLite runtime fixes.
Distribution and compatibility documentation
.config/goreleaser.yml, Makefile, scripts/install.sh, README.md, docs/guides/*, cmd/ox/guides/*, cmd/ox/release_notes.md, cmd/ox/init.go, cmd/ox/integrate.go, cmd/ox/upgrade.go, .sageox/README.md
Adds Goose to builds and installation workflows and updates compatibility, integration, setup, and release documentation.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant AdapterRuntime
  participant GooseAdapter
  participant SQLiteDatabase
  participant OpenPlugins
  AdapterRuntime->>GooseAdapter: Request session data
  GooseAdapter->>SQLiteDatabase: Query session messages
  SQLiteDatabase-->>GooseAdapter: Return parsed rows and offset
  OpenPlugins->>GooseAdapter: Deliver lifecycle hook event
  GooseAdapter-->>AdapterRuntime: Return session entries and updated offset
Loading

Possibly related PRs

  • sageox/ox#738: Provides earlier Goose documentation, integration metadata, and AGENTS.md registration changes extended by this PR.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.10% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the primary changes: adding the Goose session adapter and Open Plugins hook installer.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ryan/ox-adapter-goose

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

@socket-security

socket-security Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updatedgolang/​github.com/​sageox/​agentx@​v0.1.11 ⏵ v0.1.1397 +1100100100100

View full report

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 14

🧹 Nitpick comments (8)
cmd/ox-adapter-goose/main.go (1)

150-176: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use errors.Is(err, os.ErrNotExist) instead of os.IsNotExist(err).

Both existence checks in handleDiagnose use the older os.IsNotExist(err) idiom.

♻️ Proposed fix
-		if _, err := os.Stat(hooksPath); os.IsNotExist(err) {
+		if _, err := os.Stat(hooksPath); errors.Is(err, os.ErrNotExist) {

and

-			if _, err := os.Stat(manifestFilePath(p.RepoRoot, "project")); os.IsNotExist(err) {
+			if _, err := os.Stat(manifestFilePath(p.RepoRoot, "project")); errors.Is(err, os.ErrNotExist) {

Based on learnings, "prefer errors.Is(err, os.ErrNotExist) over os.IsNotExist(err)" for consistent error-unwrapping semantics across the codebase.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/ox-adapter-goose/main.go` around lines 150 - 176, Update both existence
checks in handleDiagnose to use errors.Is(err, os.ErrNotExist) instead of
os.IsNotExist(err), and add the required errors import if absent. Preserve the
existing issue-reporting behavior for missing hooks.json and plugin.json.

Source: Learnings

cmd/ox-adapter-goose/main_test.go (1)

1-79: 📐 Maintainability & Code Quality | 🔵 Trivial | 🏗️ Heavy lift

Missing coverage for handleDetect, handleImportSession, and handleDiagnose.

This file only tests handleInfo's static metadata. The detection heuristics (env vars, DB path, config dir, PATH lookup), the SQLite-backed import path, and the diagnose issue-generation logic in main.go are untested here.

As per coding guidelines, "*.go test files should use table-driven tests, test error paths, and maintain at least 85% coverage."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/ox-adapter-goose/main_test.go` around lines 1 - 79, Add table-driven
tests covering handleDetect’s environment, database path, config directory, and
PATH lookup heuristics, plus success and error cases for handleImportSession and
issue-generation behavior in handleDiagnose. Reuse existing test helpers and
fixtures where available, and assert both returned values and errors; keep the
existing handleInfo tests unchanged.

Source: Coding guidelines

cmd/ox-adapter-goose/session_test.go (2)

301-363: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Good coverage of the resolution precedence. One gap worth filling alongside the LIKE-escaping fix flagged in session.go (Line 145-150): add a session whose working_dir is e.g. /repo/myXrepo/sub and a repoRoot of /repo/my_repo, which must not match.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/ox-adapter-goose/session_test.go` around lines 301 - 363, The
session-resolution tests need a LIKE-pattern false-positive case. Extend the
relevant TestResolveSessionID coverage with a session whose working_dir is
/repo/myXrepo/sub and resolve using /repo/my_repo, asserting it does not match
and produces the expected no-match error; keep the existing valid subdirectory
behavior unchanged.

108-119: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the tool-entry fields, not just the count.

entryText returns Content, which is empty for tool entries, so these two cases only prove "one entry appeared". The parts that can silently regress — ToolName, ToolInput, CallID correlation between request and response, and IsError — are unchecked. Consider adding optional wantToolName/wantCallID fields to the table.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/ox-adapter-goose/session_test.go` around lines 108 - 119, Strengthen the
table-driven tests around the tool request and tool response cases in the
session test by asserting the parsed tool-entry fields rather than only
wantEntries. Extend the test expectations with optional tool name, tool input,
call ID, and error-state values, then validate them against each returned entry,
including correlation of the request and response CallID.
cmd/ox/release_notes.md (1)

12-12: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use the canonical “AI coworker” terminology in new user-facing prose.

As per coding guidelines, replace generic agent/agents wording with AI coworker/AI coworkers, while keeping literal command names such as ox agent, filenames, and product names unchanged. Also replace “transcripts” in the release note with “session records” or “session data”; Transcript is reserved for human-to-human voice discussions.

  • cmd/ox/release_notes.md#L12-L12: replace “every other agent” and “transcripts”.
  • README.md#L48-L80: rename the table heading and prose references.
  • .sageox/README.md#L68-L75: update “agent above”.
  • cmd/ox/init.go#L1342-L1349: apply the same wording in generated content.
  • cmd/ox/integrate.go#L79-L81: replace “Other agents”.
  • docs/guides/agent-compatibility.md#L16-L47: update the table and session-store prose.
  • docs/guides/agent-compatibility.md#L131-L149: update limitation and version prose.
  • cmd/ox/guides/agents-md.md#L9-L9: replace “AI coding agents”.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/ox/release_notes.md` at line 12, Update the user-facing terminology
across all listed sites: use “AI coworker” or “AI coworkers” instead of generic
“agent” or “agents,” and replace “transcripts” with “session records” or
“session data,” while preserving literal commands, filenames, and product names.
Apply the wording consistently in cmd/ox/release_notes.md:12, README.md:48-80,
.sageox/README.md:68-75, cmd/ox/init.go:1342-1349, cmd/ox/integrate.go:79-81,
docs/guides/agent-compatibility.md:16-47 and 131-149, and
cmd/ox/guides/agents-md.md:9; update headings, prose, generated content, table
text, limitations, and version references as applicable.

Source: Coding guidelines

cmd/ox-adapter-goose/hooks_test.go (2)

231-233: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use errors.Is(err, os.ErrNotExist).

Based on learnings from this repo, prefer errors.Is(err, os.ErrNotExist) over os.IsNotExist(err) for Go 1.13+ unwrapping semantics and consistency.

♻️ Proposed change
-	if _, err := os.Stat(dir); !os.IsNotExist(err) {
+	if _, err := os.Stat(dir); !errors.Is(err, os.ErrNotExist) {
 		t.Errorf("expected %s to be removed, stat err = %v", dir, err)
 	}

Add errors to the import block.

Based on learnings: "prefer errors.Is(err, os.ErrNotExist) over os.IsNotExist(err)".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/ox-adapter-goose/hooks_test.go` around lines 231 - 233, Update the
directory-removal assertion around os.Stat to use errors.Is(err, os.ErrNotExist)
instead of os.IsNotExist(err), and add the errors package to the imports.
Preserve the existing test failure message and expectation that the directory is
absent.

Source: Learnings


118-123: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a user-scope install test with t.Setenv("HOME", …).

Only hookCommand is exercised for user scope; the pluginDir user branch (home lookup + $HOME/.agents/plugins/sageox) is never covered, which is where the discarded UserHomeDir error in hooks.go hides. t.Setenv("HOME", t.TempDir()) lets you install/check/uninstall in user scope safely without touching the real home directory.

As per coding guidelines: "Prefer end-to-end reality over isolated unit testing, use table-driven tests, test error paths, and maintain at least 85% coverage."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/ox-adapter-goose/hooks_test.go` around lines 118 - 123, Add an end-to-end
user-scope install/check/uninstall test alongside
TestHookCommand_UserScopeOmitsRepoRoot, using t.Setenv("HOME", t.TempDir()) to
exercise the pluginDir user branch in hooks.go. Verify the complete lifecycle
and relevant filesystem behavior, and use table-driven coverage where
appropriate, including the UserHomeDir error path if the existing APIs permit
it.

Source: Coding guidelines

cmd/ox-adapter-goose/hooks.go (1)

265-276: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Distinguish "missing" from "corrupt", and wrap the write errors.

loadHooksFile collapses a malformed existing hooks.json into an empty file, so install silently overwrites a user's unparsable-but-real rules. Returning an error for parse failures (while still treating os.ErrNotExist as empty) makes that case visible. Separately, writeJSON returns bare MkdirAll/WriteFile errors; wrapping them with the path gives callers actionable context.

♻️ Proposed refactor
-func loadHooksFile(path string) hooksFile {
+func loadHooksFile(path string) (hooksFile, error) {
 	hf := hooksFile{Hooks: map[string][]hookRule{}}
 
 	data, err := os.ReadFile(path) //nolint:gosec // path derived from repo root + fixed plugin name
 	if err != nil {
-		return hf
+		if errors.Is(err, os.ErrNotExist) {
+			return hf, nil
+		}
+		return hf, fmt.Errorf("failed to read %s: %w", path, err)
 	}
-	if err := json.Unmarshal(data, &hf); err != nil || hf.Hooks == nil {
-		return hooksFile{Hooks: map[string][]hookRule{}}
+	if err := json.Unmarshal(data, &hf); err != nil {
+		return hf, fmt.Errorf("failed to parse %s: %w", path, err)
 	}
-	return hf
+	if hf.Hooks == nil {
+		hf.Hooks = map[string][]hookRule{}
+	}
+	return hf, nil
 }
 func writeJSON(path string, v any) error {
 	if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
-		return err
+		return fmt.Errorf("failed to create %s: %w", filepath.Dir(path), err)
 	}
 	data, err := json.MarshalIndent(v, "", "  ")
 	if err != nil {
-		return err
+		return fmt.Errorf("failed to marshal %s: %w", path, err)
 	}
 	data = append(data, '\n')
-	return os.WriteFile(path, data, 0o644) //nolint:gosec // config file, not a secret
+	if err := os.WriteFile(path, data, 0o644); err != nil { //nolint:gosec // config file, not a secret
+		return fmt.Errorf("failed to write %s: %w", path, err)
+	}
+	return nil
 }

Requires adding errors to the imports.

As per coding guidelines: "Use `errors.Is()` and `errors.As()` for error inspection and wrap errors with contextual information."

Also applies to: 327-337

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/ox-adapter-goose/hooks.go` around lines 265 - 276, Update loadHooksFile
to return an error alongside hooksFile: treat os.ErrNotExist from os.ReadFile as
an empty hooks file, but propagate JSON parse failures with contextual wrapping
instead of silently resetting rules. Update its callers to handle the new error
contract. In writeJSON, wrap MkdirAll and WriteFile failures with the target
path, using errors.Is/errors.As for error inspection as appropriate, and add the
errors import.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmd/ox-adapter-goose/hooks.go`:
- Around line 117-124: Make pluginDir return (string, error), propagate
os.UserHomeDir failures, and reject an empty root before joining the plugin
path; update hooksFilePath and manifestFilePath to handle the new result. At
cmd/ox-adapter-goose/hooks.go:117-124, implement the root validation; at
:173-175, propagate the pluginDir error or guard empty project RepoRoot before
manifest stat; at :204-205, apply the same guard before reading hooks.json so
uninstall cannot RemoveAll a cwd-relative path.

In `@cmd/ox-adapter-goose/main.go`:
- Around line 95-126: Handle the error returned by readMetadata in
handleImportSession instead of discarding it; return a contextual error when
metadata loading fails, and only construct ImportSessionResult with the metadata
after a successful read.

In `@cmd/ox-adapter-goose/serve.go`:
- Around line 19-46: Wrap errors returned by handleFindSession in the
OnFindSession callback and handleReadFromOffset in the OnReadFromOffset callback
with operation-specific context using %w, while preserving the original causes
for inspection and leaving successful results unchanged.

In `@cmd/ox-adapter-goose/session.go`:
- Around line 391-393: Update the "toolResponse" branch to skip the block when
b.ToolResp is nil, matching the existing guard in the "toolRequest" branch. Only
call toolResponseFields and append adapterruntime.ToolResultWithID when a
payload is present, preserving correlation through b.ID for valid responses.
- Around line 248-261: Update the session read flow around readMessages and
maxMessageID so the watermark is derived from the maximum message ID actually
returned by readMessages, eliminating the separate maxMessageID query and
backwards clamp. Extend readMessages to select and return the relevant ID
alongside the entries, and update its callers to handle the additional return
value, using the returned ID as the new offset.
- Around line 145-150: The repository prefix used in the session query must be
escaped before constructing the LIKE pattern. Add or reuse an escapeLike helper
that neutralizes backslashes, percent signs, and underscores, then use the
escaped repoRoot for the wildcard argument while keeping the exact working_dir
argument unchanged.
- Around line 152-162: Update the session query construction around the since
filter to normalize updated_at to a consistent datetime representation before
applying the >= comparison, or bind since using Goose’s RFC3339 storage format.
Preserve existing ordering and limit behavior, and add coverage verifying
filtering works with both RFC3339 and YYYY-MM-DD HH:MM:SS stored values.
- Around line 92-98: Update the database-opening logic in the session database
helper to construct a properly escaped file: URI from dbPath, preserving mode=ro
and optionally adding query_only(true). Remove the journal_mode(WAL) pragma so
opening the Goose database cannot persistently modify sessions.db, while
retaining the existing error wrapping and return behavior.

In `@cmd/ox/guides/agents-md.md`:
- Line 9: Update the reader list in the AGENTS.md guidance to include Gemini CLI
and remove OpenAI Agents SDK, matching the project’s documented supported-reader
list and surrounding GEMINI.md guidance. Preserve the existing explanation that
AGENTS.md is the converged repository-root convention.

In `@cmd/ox/guides/getting-started.md`:
- Line 9: Use canonical “AI coworker” terminology in all three guide locations:
in cmd/ox/guides/getting-started.md:9 replace “any other supported coding agent”
with “any other supported AI coworker”; in cmd/ox/guides/murmur-vs-rule.md:40
replace “supported coding agent” similarly; and in
cmd/ox/guides/team-rules.md:16 rewrite “coding agents” and “agent-agnostic”
using “AI coworkers” wording while preserving the literal command “ox agent
prime”.

In `@cmd/ox/init.go`:
- Around line 1342-1346: Update the generated guidance in cmd/ox/init.go around
the agent integration list to distinguish SessionStart lifecycle hooks from
instruction-file marker-based initial priming, explicitly reflecting that Gemini
CLI and Droid use markers for priming despite having other hooks. Apply the same
wording to the checked-in copy in .sageox/README.md; both listed sites require
the corresponding guidance update.

In `@cmd/ox/release_notes.md`:
- Line 12: Update the Goose release-note heading in the documented support
statement to say “Silver-tier support” or “supported Goose integration” instead
of “fully supported,” while preserving the remaining implementation details and
the caveat about missing compaction-event recovery.
- Line 12: The release note’s comparison incorrectly claims every other agent
uses a single settings file. Update the Goose integration description to call
its directory-based approach ox’s first Open Plugins manifest/hooks directory,
while preserving the existing explanation of Goose’s plugin location and hook
behavior.

In `@docs/guides/agent-compatibility.md`:
- Around line 133-136: Rename the customer-facing OX_PROJECT_ROOT environment
variable to the appropriate SAGEOX_* name across the Goose adapter, project-root
discovery logic, tests, and documentation. Update every producer, consumer,
assertion, and export instruction consistently while preserving the existing
absolute repository-root behavior.

---

Nitpick comments:
In `@cmd/ox-adapter-goose/hooks_test.go`:
- Around line 231-233: Update the directory-removal assertion around os.Stat to
use errors.Is(err, os.ErrNotExist) instead of os.IsNotExist(err), and add the
errors package to the imports. Preserve the existing test failure message and
expectation that the directory is absent.
- Around line 118-123: Add an end-to-end user-scope install/check/uninstall test
alongside TestHookCommand_UserScopeOmitsRepoRoot, using t.Setenv("HOME",
t.TempDir()) to exercise the pluginDir user branch in hooks.go. Verify the
complete lifecycle and relevant filesystem behavior, and use table-driven
coverage where appropriate, including the UserHomeDir error path if the existing
APIs permit it.

In `@cmd/ox-adapter-goose/hooks.go`:
- Around line 265-276: Update loadHooksFile to return an error alongside
hooksFile: treat os.ErrNotExist from os.ReadFile as an empty hooks file, but
propagate JSON parse failures with contextual wrapping instead of silently
resetting rules. Update its callers to handle the new error contract. In
writeJSON, wrap MkdirAll and WriteFile failures with the target path, using
errors.Is/errors.As for error inspection as appropriate, and add the errors
import.

In `@cmd/ox-adapter-goose/main_test.go`:
- Around line 1-79: Add table-driven tests covering handleDetect’s environment,
database path, config directory, and PATH lookup heuristics, plus success and
error cases for handleImportSession and issue-generation behavior in
handleDiagnose. Reuse existing test helpers and fixtures where available, and
assert both returned values and errors; keep the existing handleInfo tests
unchanged.

In `@cmd/ox-adapter-goose/main.go`:
- Around line 150-176: Update both existence checks in handleDiagnose to use
errors.Is(err, os.ErrNotExist) instead of os.IsNotExist(err), and add the
required errors import if absent. Preserve the existing issue-reporting behavior
for missing hooks.json and plugin.json.

In `@cmd/ox-adapter-goose/session_test.go`:
- Around line 301-363: The session-resolution tests need a LIKE-pattern
false-positive case. Extend the relevant TestResolveSessionID coverage with a
session whose working_dir is /repo/myXrepo/sub and resolve using /repo/my_repo,
asserting it does not match and produces the expected no-match error; keep the
existing valid subdirectory behavior unchanged.
- Around line 108-119: Strengthen the table-driven tests around the tool request
and tool response cases in the session test by asserting the parsed tool-entry
fields rather than only wantEntries. Extend the test expectations with optional
tool name, tool input, call ID, and error-state values, then validate them
against each returned entry, including correlation of the request and response
CallID.

In `@cmd/ox/release_notes.md`:
- Line 12: Update the user-facing terminology across all listed sites: use “AI
coworker” or “AI coworkers” instead of generic “agent” or “agents,” and replace
“transcripts” with “session records” or “session data,” while preserving literal
commands, filenames, and product names. Apply the wording consistently in
cmd/ox/release_notes.md:12, README.md:48-80, .sageox/README.md:68-75,
cmd/ox/init.go:1342-1349, cmd/ox/integrate.go:79-81,
docs/guides/agent-compatibility.md:16-47 and 131-149, and
cmd/ox/guides/agents-md.md:9; update headings, prose, generated content, table
text, limitations, and version references as applicable.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 45679a4b-0734-48f9-a4d0-2582d6d39f55

📥 Commits

Reviewing files that changed from the base of the PR and between 4c451dd and 302ee4d.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (34)
  • .config/goreleaser.yml
  • .sageox/README.md
  • Makefile
  • README.md
  • cmd/ox-adapter-goose/hooks.go
  • cmd/ox-adapter-goose/hooks_test.go
  • cmd/ox-adapter-goose/main.go
  • cmd/ox-adapter-goose/main_test.go
  • cmd/ox-adapter-goose/serve.go
  • cmd/ox-adapter-goose/session.go
  • cmd/ox-adapter-goose/session_test.go
  • cmd/ox/doctor_adapter_blocks.go
  • cmd/ox/doctor_agent.go
  • cmd/ox/guides/agents-md.md
  • cmd/ox/guides/getting-started.md
  • cmd/ox/guides/murmur-vs-rule.md
  • cmd/ox/guides/team-rules.md
  • cmd/ox/init.go
  • cmd/ox/instruction_files.go
  • cmd/ox/instruction_files_test.go
  • cmd/ox/integrate.go
  • cmd/ox/release_notes.md
  • cmd/ox/upgrade.go
  • docs/guides/agent-compatibility.md
  • go.mod
  • internal/adapter/registry.yaml
  • internal/adapter/registry_test.go
  • internal/prime/agent_type.go
  • internal/prime/conformance_test.go
  • internal/session/adapters/adapter.go
  • internal/session/adapters/external_symlink_test.go
  • internal/session/adapters/generic_jsonl_test.go
  • internal/session/adapters/session_roots.go
  • scripts/install.sh

Comment thread cmd/ox-adapter-goose/hooks.go Outdated
Comment thread cmd/ox-adapter-goose/main.go
Comment thread cmd/ox-adapter-goose/serve.go
Comment thread cmd/ox-adapter-goose/session.go
Comment thread cmd/ox-adapter-goose/session.go
Comment thread cmd/ox/guides/agents-md.md Outdated
Comment thread cmd/ox/guides/getting-started.md Outdated
Comment thread cmd/ox/init.go Outdated
Comment thread cmd/ox/release_notes.md Outdated
Comment thread docs/guides/agent-compatibility.md
@rsnodgrass
rsnodgrass force-pushed the ryan/ox-adapter-goose branch from c6d8573 to 987c762 Compare July 31, 2026 01:15

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 2

♻️ Duplicate comments (3)
cmd/ox-adapter-goose/main.go (1)

95-126: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Still discarding the metadata-read error in handleImportSession.

meta, _ := readMetadata(db, p.SessionID) (line 120) silently swallows the error; a corrupted or partially-written metadata row will produce an empty Metadata instead of surfacing the failure.

🩹 Proposed fix
-	meta, _ := readMetadata(db, p.SessionID)
+	meta, err := readMetadata(db, p.SessionID)
+	if err != nil {
+		return nil, fmt.Errorf("reading session metadata: %w", err)
+	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/ox-adapter-goose/main.go` around lines 95 - 126, Update
handleImportSession to capture and handle the error returned by readMetadata.
Return a contextual error when metadata loading fails, and only construct
ImportSessionResult with the metadata after a successful read.
cmd/ox-adapter-goose/hooks.go (1)

117-124: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Unfixed: pluginDir can still resolve to a CWD-relative path, and handleUninstallHooks will RemoveAll it.

os.UserHomeDir()'s error is discarded on line 120, and only handleInstallHooks (137-139) requires a non-empty RepoRoot for project scope — handleCheckHooks and handleUninstallHooks don't. If either is invoked with project scope and an empty RepoRoot (or user scope with $HOME unset), pluginDir returns .agents/plugins/sageox relative to the process's CWD, and the destructive os.RemoveAll(dir) at line 237 would delete that CWD-relative directory instead of erroring out.

🐛 Proposed fix
-func pluginDir(repoRoot, scope string) string {
-	root := repoRoot
-	if scope == scopeUser {
-		home, _ := os.UserHomeDir()
-		root = home
-	}
-	return filepath.Join(root, ".agents", "plugins", pluginName)
-}
+func pluginDir(repoRoot, scope string) (string, error) {
+	root := repoRoot
+	if scope == scopeUser {
+		home, err := os.UserHomeDir()
+		if err != nil {
+			return "", fmt.Errorf("resolving user home: %w", err)
+		}
+		root = home
+	} else if root == "" {
+		return "", fmt.Errorf("repo root is required for scope %q", scope)
+	}
+	return filepath.Join(root, ".agents", "plugins", pluginName), nil
+}

Propagate the new (string, error) through hooksFilePath/manifestFilePath and their callers in handleCheckHooks and handleUninstallHooks.

Also applies to: 173-175, 204-205

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/ox-adapter-goose/hooks.go` around lines 117 - 124, Update pluginDir to
return (string, error) and reject project scope with an empty repoRoot or user
scope when os.UserHomeDir fails or returns empty, rather than producing a
relative path. Propagate this error through hooksFilePath and manifestFilePath,
then handle it in handleCheckHooks and handleUninstallHooks before any
filesystem access, especially the destructive os.RemoveAll call.
cmd/ox-adapter-goose/serve.go (1)

19-48: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Still not wrapping delegated handler errors with context.

Lines 22 and 43 return handleFindSession/handleReadFromOffset errors unchanged. As per coding guidelines, "wrap errors with contextual information."

🩹 Proposed fix
 	srv.OnFindSession(func(ctx context.Context, p adapterprotocol.FindSessionParams) (*adapterprotocol.FindSessionResult, error) {
 		result, err := handleFindSession(p)
 		if err != nil {
-			return nil, err
+			return nil, fmt.Errorf("find Goose session: %w", err)
 		}
@@
 		result, err := handleReadFromOffset(p)
 		if err != nil {
-			return nil, err
+			return nil, fmt.Errorf("read Goose session: %w", err)
 		}

(add "fmt" to imports)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/ox-adapter-goose/serve.go` around lines 19 - 48, The delegated handler
errors in the OnFindSession and OnReadFromOffset callbacks are returned without
context. Import fmt and wrap each err from handleFindSession and
handleReadFromOffset with a descriptive operation-specific message before
returning it.

Source: Coding guidelines

🧹 Nitpick comments (5)
cmd/ox-adapter-goose/session_test.go (2)

291-296: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Don't discard readMessages errors here.

A query failure surfaces as a confusing length mismatch instead of the real cause.

♻️ Proposed tweak
-	if replayed, _ := readMessages(db, "s1", count); len(replayed) != 1 {
+	replayed, err := readMessages(db, "s1", count)
+	if err != nil {
+		t.Fatalf("readMessages from count watermark: %v", err)
+	}
+	if len(replayed) != 1 {
 		t.Errorf("count-based watermark should replay 1 entry (that is the bug), got %d", len(replayed))
 	}
-	if fresh, _ := readMessages(db, "s1", got); len(fresh) != 0 {
+	fresh, err := readMessages(db, "s1", got)
+	if err != nil {
+		t.Fatalf("readMessages from rowid watermark: %v", err)
+	}
+	if len(fresh) != 0 {
 		t.Errorf("rowid watermark must replay nothing, got %d", len(fresh))
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/ox-adapter-goose/session_test.go` around lines 291 - 296, Update the
assertions around readMessages in the watermark test to capture and check its
returned error before validating message counts. Report query failures directly
through the test framework, while preserving the existing replay-count
assertions for successful reads.

108-119: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Tool cases assert only the entry count.

wantFirst is empty for the tool request/tool response cases, so the if tt.wantFirst != "" guard skips the only content assertion — ToolName, ToolInput, IsError, and CallID are never checked, and the correlation ID contract from adapterruntime.ToolUseWithID/ToolResultWithID goes untested. Add expected fields to the table and assert them unconditionally.

💚 Proposed test tightening
 	tests := []struct {
 		name        string
 		role        string
 		content     string
 		wantEntries int
 		wantFirst   string
+		wantTool    string
+		wantCallID  string
 	}{
@@
 		{
 			name:        "tool request",
 			role:        "assistant",
 			content:     `[{"type":"toolRequest","id":"call_1","toolCall":{"status":"success","value":{"name":"developer__shell","arguments":{"command":"ls"}}}}]`,
 			wantEntries: 1,
+			wantTool:    "developer__shell",
+			wantCallID:  "call_1",
 		},
if tt.wantTool != "" && got[0].ToolName != tt.wantTool {
	t.Errorf("ToolName = %q, want %q", got[0].ToolName, tt.wantTool)
}
if tt.wantCallID != "" && got[0].CallID != tt.wantCallID {
	t.Errorf("CallID = %q, want %q", got[0].CallID, tt.wantCallID)
}

Based on learnings, avoid assertions inside conditionals that can skip them and let a test pass vacuously.

Also applies to: 142-152

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/ox-adapter-goose/session_test.go` around lines 108 - 119, Strengthen the
table-driven tests around the tool request and tool response cases in the
session test by adding expected tool name, input, error status, and call ID
fields, then assert every field unconditionally after parsing. Update the
existing wantFirst-based assertions and related test symbols so both
ToolUseWithID and ToolResultWithID correlation IDs and payloads are verified
rather than allowing empty expectations to skip validation.

Source: Learnings

cmd/ox-adapter-goose/session.go (2)

54-58: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Dead Raw field and a comment that undersells the group.

Raw is tagged json:"-" and never assigned or read; and the toolRequest comment sits above ToolResp (toolResult), which belongs to toolResponse blocks.

♻️ Proposed cleanup
-	// type == "toolRequest"
-	ID       string          `json:"id"`
-	ToolCall *gooseToolCall  `json:"toolCall"`
-	ToolResp *gooseToolResp  `json:"toolResult"`
-	Raw      json.RawMessage `json:"-"`
+	// Correlation ID shared by a toolRequest/toolResponse pair.
+	ID string `json:"id"`
+
+	// type == "toolRequest"
+	ToolCall *gooseToolCall `json:"toolCall"`
+
+	// type == "toolResponse"
+	ToolResp *gooseToolResp `json:"toolResult"`
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/ox-adapter-goose/session.go` around lines 54 - 58, Remove the unused Raw
field from the relevant session struct, and move or revise the nearby type
comment so it describes the ToolCall/toolRequest fields without grouping
ToolResp/toolResult under them. Preserve the existing JSON field names and
struct behavior.

84-99: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Make the DB path injectable so the protocol handlers can be tested.

openDB resolves a process-global path, so handleFindSession, handleRead, handleReadFromOffset, handleReadMetadata, and handleCapturePrior are untestable — the test file exercises only the pure helpers. Splitting out an openDBAt(path string) (or a package-level dbPathFn hook) lets the fixture DB drive the full handler surface. While here, wrap the os.Stat error instead of dropping it; a permission failure currently reports "not found".

♻️ Proposed refactor
 func openDB() (*sql.DB, error) {
 	dbPath := gooseDBPath()
 	if dbPath == "" {
 		return nil, fmt.Errorf("goose data directory not found")
 	}
+	return openDBAt(dbPath)
+}
+
+// openDBAt opens a specific sessions.db. Split out so tests can point at a fixture.
+func openDBAt(dbPath string) (*sql.DB, error) {
 	if _, err := os.Stat(dbPath); err != nil {
+		if errors.Is(err, os.ErrNotExist) {
+			return nil, fmt.Errorf("goose sessions.db not found at %s", dbPath)
+		}
-		return nil, fmt.Errorf("goose sessions.db not found at %s", dbPath)
+		return nil, fmt.Errorf("stat goose sessions.db at %s: %w", dbPath, err)
 	}

Based on learnings, prefer errors.Is(err, os.ErrNotExist) for file-not-found checks. As per coding guidelines, **/*_test.go must "maintain at least 85% coverage".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/ox-adapter-goose/session.go` around lines 84 - 99, Refactor openDB into
an injectable openDBAt(path string) helper, while retaining openDB’s gooseDBPath
resolution for production callers, so handlers including handleFindSession,
handleRead, handleReadFromOffset, handleReadMetadata, and handleCapturePrior can
use fixture database paths. In openDBAt, distinguish os.ErrNotExist with
errors.Is and wrap other os.Stat failures with their original error details
instead of reporting them as missing; preserve the existing read-only SQLite
options.

Sources: Coding guidelines, Learnings

cmd/ox-adapter-goose/main.go (1)

150-176: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use errors.Is(err, os.ErrNotExist) instead of os.IsNotExist(err) across both sites. Same root cause: this repo's convention (per coding guidelines and a prior learning) is errors.Is(err, os.ErrNotExist) for file-not-found checks, since it correctly unwraps wrapped errors per Go 1.13+ semantics.

  • cmd/ox-adapter-goose/main.go#L150-L176: replace os.IsNotExist(err) at lines 152 and 165 with errors.Is(err, os.ErrNotExist) (requires importing "errors").
  • cmd/ox-adapter-goose/hooks_test.go#L220-L239: replace !os.IsNotExist(err) at line 231 with !errors.Is(err, os.ErrNotExist) (requires importing "errors").
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/ox-adapter-goose/main.go` around lines 150 - 176, Replace os.IsNotExist
with errors.Is(err, os.ErrNotExist) in the two checks around hooksFilePath and
manifestFilePath in cmd/ox-adapter-goose/main.go, adding the errors import.
Apply the same replacement to the file-not-found assertion in
cmd/ox-adapter-goose/hooks_test.go, also adding errors there.

Sources: Coding guidelines, Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.sageox/README.md:
- Around line 68-72: Update the agent support list in the README to distinguish
automatic recording or lifecycle-hook integration from automatic priming.
Specifically, remove Gemini CLI and Droid from the automatic-priming implication
in that bullet and describe their hook-based recording while preserving the
marker-based priming guidance consistent with the compatibility matrix and
limitation notes.

In `@cmd/ox-adapter-goose/session.go`:
- Around line 311-321: Normalize createdTS in the session message row loop
before constructing ts: when its value exceeds 10,000,000,000, divide it by
1,000 to convert milliseconds to seconds, then pass the normalized value to
time.Unix. Keep existing scanning and parseContentBlocks behavior unchanged.

---

Duplicate comments:
In `@cmd/ox-adapter-goose/hooks.go`:
- Around line 117-124: Update pluginDir to return (string, error) and reject
project scope with an empty repoRoot or user scope when os.UserHomeDir fails or
returns empty, rather than producing a relative path. Propagate this error
through hooksFilePath and manifestFilePath, then handle it in handleCheckHooks
and handleUninstallHooks before any filesystem access, especially the
destructive os.RemoveAll call.

In `@cmd/ox-adapter-goose/main.go`:
- Around line 95-126: Update handleImportSession to capture and handle the error
returned by readMetadata. Return a contextual error when metadata loading fails,
and only construct ImportSessionResult with the metadata after a successful
read.

In `@cmd/ox-adapter-goose/serve.go`:
- Around line 19-48: The delegated handler errors in the OnFindSession and
OnReadFromOffset callbacks are returned without context. Import fmt and wrap
each err from handleFindSession and handleReadFromOffset with a descriptive
operation-specific message before returning it.

---

Nitpick comments:
In `@cmd/ox-adapter-goose/main.go`:
- Around line 150-176: Replace os.IsNotExist with errors.Is(err, os.ErrNotExist)
in the two checks around hooksFilePath and manifestFilePath in
cmd/ox-adapter-goose/main.go, adding the errors import. Apply the same
replacement to the file-not-found assertion in
cmd/ox-adapter-goose/hooks_test.go, also adding errors there.

In `@cmd/ox-adapter-goose/session_test.go`:
- Around line 291-296: Update the assertions around readMessages in the
watermark test to capture and check its returned error before validating message
counts. Report query failures directly through the test framework, while
preserving the existing replay-count assertions for successful reads.
- Around line 108-119: Strengthen the table-driven tests around the tool request
and tool response cases in the session test by adding expected tool name, input,
error status, and call ID fields, then assert every field unconditionally after
parsing. Update the existing wantFirst-based assertions and related test symbols
so both ToolUseWithID and ToolResultWithID correlation IDs and payloads are
verified rather than allowing empty expectations to skip validation.

In `@cmd/ox-adapter-goose/session.go`:
- Around line 54-58: Remove the unused Raw field from the relevant session
struct, and move or revise the nearby type comment so it describes the
ToolCall/toolRequest fields without grouping ToolResp/toolResult under them.
Preserve the existing JSON field names and struct behavior.
- Around line 84-99: Refactor openDB into an injectable openDBAt(path string)
helper, while retaining openDB’s gooseDBPath resolution for production callers,
so handlers including handleFindSession, handleRead, handleReadFromOffset,
handleReadMetadata, and handleCapturePrior can use fixture database paths. In
openDBAt, distinguish os.ErrNotExist with errors.Is and wrap other os.Stat
failures with their original error details instead of reporting them as missing;
preserve the existing read-only SQLite options.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 2a988663-437d-4de4-b370-90f1ac142a16

📥 Commits

Reviewing files that changed from the base of the PR and between c6d8573 and 987c762.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (27)
  • .config/goreleaser.yml
  • .sageox/README.md
  • Makefile
  • README.md
  • cmd/ox-adapter-goose/hooks.go
  • cmd/ox-adapter-goose/hooks_test.go
  • cmd/ox-adapter-goose/main.go
  • cmd/ox-adapter-goose/main_test.go
  • cmd/ox-adapter-goose/serve.go
  • cmd/ox-adapter-goose/session.go
  • cmd/ox-adapter-goose/session_test.go
  • cmd/ox/doctor_adapter_blocks.go
  • cmd/ox/doctor_agent.go
  • cmd/ox/init.go
  • cmd/ox/release_notes.md
  • cmd/ox/upgrade.go
  • docs/guides/agent-compatibility.md
  • go.mod
  • internal/adapter/registry.yaml
  • internal/adapter/registry_test.go
  • internal/prime/agent_type.go
  • internal/prime/conformance_test.go
  • internal/session/adapters/adapter.go
  • internal/session/adapters/external_symlink_test.go
  • internal/session/adapters/generic_jsonl_test.go
  • internal/session/adapters/session_roots.go
  • scripts/install.sh
🚧 Files skipped from review as they are similar to previous changes (6)
  • scripts/install.sh
  • cmd/ox/doctor_agent.go
  • Makefile
  • cmd/ox/init.go
  • README.md
  • cmd/ox/upgrade.go

Comment thread .sageox/README.md Outdated
Comment thread cmd/ox-adapter-goose/session.go
@rsnodgrass
rsnodgrass marked this pull request as ready for review July 31, 2026 01:23
@rsnodgrass

Copy link
Copy Markdown
Contributor Author

Addressed the review. Four findings were real and are fixed; two I skipped with reasons.

Fixed

  • pluginDir could resolve relative → os.RemoveAll on cwd. The worst of the four. pluginDir now returns (string, error), propagates the os.UserHomeDir() failure, and rejects both an empty and a non-absolute root. check and uninstall previously had no repo-root guard at all — both now do. Covered by TestPluginDir_RefusesRelativePaths, TestHandlers_RejectUnanchoredScopeRoot, and an end-to-end TestUninstallHooks_DoesNotTouchCwd that chdirs into a directory containing a real plugin dir and proves it survives.
  • Unescaped LIKE wildcards in repoRoot. /repo/my_app matched /repo/myXapp/..., which would attribute another repo's transcript to this Ledger. Now ESCAPE '\\' with an escapeLike helper. TestResolveSessionID_EscapesLikeWildcards seeds a newer decoy in the wildcard-matching repo so a leak fails the test.
  • Watermark race. You were right that the second MAX(id) query drops concurrently-inserted rows permanently — Goose writes while ox reads. readMessages now returns the highest rowid it actually read, and that is the watermark. The backwards clamp is gone as redundant, as you noted. TestReadFromOffset_WatermarkComesFromRowsRead simulates the interleaving directly.
  • toolResponse with no payload now skips instead of emitting an uncorrelatable entry with empty output and no CallID.

Skipped

  • main.go:126 discarded metadata errorreadMetadata returns sql.ErrNoRows for a session with no model recorded, which is normal, not a failure. Surfacing it would fail imports for ordinary sessions. Metadata is optional in ImportSessionResult.
  • serve.go error wrapping — the handlers already return errors that name their operation, and this file matches ox-adapter-opencode/serve.go line for line. Diverging one adapter's serve shim is churn without a diagnostic gain.

Also added, prompted by the review: ox doctor now scans .agents/plugins/*/{hooks,scripts} for suspicious hook content. Unlike .claude/ or .codex/, that directory is a shared namespace — any tool can install a plugin there and Goose runs its command via sh -c. The scan covers every installed plugin, not just ox's own. Plus six diagnose tests, including the orphaned-hooks.json case where hooks look installed but Goose never loads them.

make lint clean, make test 17172 pass. Verified against the real binary: unanchored uninstall refuses, and diagnose reports manifest-missing on an orphaned hooks.json.

@coderabbitai coderabbitai Bot 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
cmd/ox-adapter-goose/main.go (1)

173-182: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Use errors.Is(err, os.ErrNotExist) instead of os.IsNotExist.

This newly added check uses the legacy os.IsNotExist(err) idiom, which doesn't unwrap wrapped errors.

🩹 Proposed fix
-			if _, err := os.Stat(manifestPath); os.IsNotExist(err) {
+			if _, err := os.Stat(manifestPath); errors.Is(err, os.ErrNotExist) {

As per coding guidelines, **/*.go: "Use errors.Is() and errors.As() for error inspection and wrap errors with contextual information." Based on learnings, "prefer errors.Is(err, os.ErrNotExist) over os.IsNotExist(err)" is the established convention in this repo.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/ox-adapter-goose/main.go` around lines 173 - 182, The manifest existence
check in the diagnostic flow uses the legacy os.IsNotExist inspection. Update
the os.Stat error condition around manifestPath to use errors.Is with
os.ErrNotExist, and add or reuse the required errors import while preserving the
existing issue creation behavior.

Sources: Coding guidelines, Learnings

cmd/ox-adapter-goose/session.go (1)

82-99: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Open Goose’s database with a real read-only URI. modernc.org/sqlite strips query parameters from DSNs without a file: prefix, then opens them with READWRITE|CREATE; therefore mode=ro is ignored and _pragma=journal_mode(WAL) can mutate sessions.db. Construct a proper escaped file: URI or use another driver-supported read-only mechanism.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/ox-adapter-goose/session.go` around lines 82 - 99, Update openDB to
construct a modernc.org/sqlite-compatible escaped file: URI for the database
path, preserving mode=ro and the required pragmas in the URI query. Ensure
sql.Open receives this file-prefixed read-only DSN so it cannot create or modify
sessions.db.
♻️ Duplicate comments (1)
cmd/ox-adapter-goose/session.go (1)

303-341: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Millisecond created_timestamp values still aren't normalized.

A prior review comment on this same function (previously lines 311-321, researched against upstream Goose source) found that created_timestamp can be stored in either seconds or milliseconds, and values above 10_000_000_000 must be divided by 1,000 before time.Unix. That fix wasn't applied here even though this function was otherwise substantially rewritten for the watermark fix — line 332 still does time.Unix(createdTS, 0).UTC() unconditionally, which would produce invalid far-future timestamps for millisecond-backed rows.

🩹 Proposed fix
 		ts := time.Unix(createdTS, 0).UTC()
+		if createdTS > 10_000_000_000 {
+			ts = time.UnixMilli(createdTS).UTC()
+		}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/ox-adapter-goose/session.go` around lines 303 - 341, Normalize createdTS
in readMessages before constructing ts: when it exceeds 10,000,000,000, divide
it by 1,000, then pass the normalized seconds value to time.Unix. Preserve
existing UTC conversion and parsing behavior for second-based timestamps.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@cmd/ox-adapter-goose/main.go`:
- Around line 173-182: The manifest existence check in the diagnostic flow uses
the legacy os.IsNotExist inspection. Update the os.Stat error condition around
manifestPath to use errors.Is with os.ErrNotExist, and add or reuse the required
errors import while preserving the existing issue creation behavior.

In `@cmd/ox-adapter-goose/session.go`:
- Around line 82-99: Update openDB to construct a modernc.org/sqlite-compatible
escaped file: URI for the database path, preserving mode=ro and the required
pragmas in the URI query. Ensure sql.Open receives this file-prefixed read-only
DSN so it cannot create or modify sessions.db.

---

Duplicate comments:
In `@cmd/ox-adapter-goose/session.go`:
- Around line 303-341: Normalize createdTS in readMessages before constructing
ts: when it exceeds 10,000,000,000, divide it by 1,000, then pass the normalized
seconds value to time.Unix. Preserve existing UTC conversion and parsing
behavior for second-based timestamps.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 261f9ef5-9b30-44b0-91ff-8913f5960e57

📥 Commits

Reviewing files that changed from the base of the PR and between 987c762 and 24f7385.

📒 Files selected for processing (8)
  • cmd/ox-adapter-goose/diagnose_test.go
  • cmd/ox-adapter-goose/hooks.go
  • cmd/ox-adapter-goose/hooks_test.go
  • cmd/ox-adapter-goose/main.go
  • cmd/ox-adapter-goose/session.go
  • cmd/ox-adapter-goose/session_test.go
  • cmd/ox/hook_content_validator.go
  • cmd/ox/hook_content_validator_test.go

@greptile-apps

greptile-apps Bot commented Jul 31, 2026

Copy link
Copy Markdown

Greptile Summary

This change adds Goose session recording, Open Plugins hook lifecycle support, and distribution wiring. A real project-scope uninstall was run against an isolated repository where the Goose hook file was a symlink to an external writable file. Uninstall completed successfully and rewrote that external file while preserving the symlink. Do not merge until project hook paths are protected from symlink redirection during uninstall.

Confidence Score: 3/5

Security Review

Project-scope Goose hook removal can follow a repository-controlled symlink at .agents/plugins/sageox/hooks/hooks.json and rewrite its external target. The reproduced flow preserves non-ox hook configuration while removing ox configuration, making the external file modification observable. Uninstall must reject symlinked repository hook paths before reading or writing them.

T-Rex T-Rex Logs

What T-Rex did

  • T-Rex produced proofs for two posted P1 findings.
  • General contract validation shows that uninstall rewrites the external target as described, with the pre-state having the external target containing the retained rule and the post-state showing the rewritten target; the adapter build completed successfully.

View all artifacts

T-Rex Ran code and verified through T-Rex

Comments Outside Diff (1)

  1. General comment

    P1 Goose project uninstall rewrites an external hooks target through a repository symlink

    • Bug
      • uninstall-hooks follows .agents/plugins/sageox/hooks/hooks.json when it is a repository-derived symlink. With a retained non-ox rule, it calls writeHooksFile, and os.WriteFile rewrites the symlink's external target. The live repro preserved echo retained-non-ox-rule, removed the ox command, and changed the external file while the repository path remained a symlink.
    • Cause
      • handleUninstallHooks reads and writes hooksPath without the symlink validation used by handleInstallHooks; its retained-rule branch at hooks.go lines 345-347 reaches writeHooksFile on the symlink path.
    • Fix
      • Before reading or writing project-scope hook/manifest paths during uninstall, apply equivalent no-symlink validation (including the final hooks.json component) and return an error without modifying files when a symlink is present.

    T-Rex Ran code and verified through T-Rex

Fix All in Claude Code

Reviews (2): Last reviewed commit: "fix(goose): address PR review — symlinks..." | Re-trigger Greptile

Comment thread cmd/ox-adapter-goose/hooks.go
Comment thread cmd/ox-adapter-goose/hooks.go
…x, timestamp normalization, opencode ReadFromOffset

Co-Authored-By: SageOx <ox@sageox.ai>
SageOx-Session: https://sageox.ai/c/ses_019fb5d3-fa51-7392-8628-c7a18f169e62
@rsnodgrass

Copy link
Copy Markdown
Contributor Author

Addressed all open review threads in 7000eaec. Summary of decisions:

Fixed:

  • main.gometa, _ := readMetadata(...) now logs a warning via slog.Warn instead of silently discarding
  • serve.go — both delegated errors now wrapped with fmt.Errorf("... %w", err) for diagnostics
  • session.go (Goose) — openDB DSN now uses file: prefix; created_timestamp normalized (values > 10 billion treated as milliseconds via time.UnixMilli)
  • hooks.go — (a) pluginDir refactored to expose scopeRoot() helper; (b) verifyNoSymlinks() added and called before any write in handleInstallHooks, rejecting repo-controlled symlinks that could redirect writes outside the repo boundary; (c) install now refuses to overwrite a foreign manifest (one without x-ox-managed: true), preventing uninstall from later destroying another tool's plugin directory — three new tests cover all three cases
  • Docs — "coding agent" → "AI coworker" across four guide files; AGENTS.md reader list updated; Gemini CLI added, OpenAI Agents SDK removed; release note reflects Silver tier; Gemini CLI / Droid now correctly described as "hooks for recording; initial prime via marker" vs. Claude Code / Codex / Goose which truly auto-prime at SessionStart

Skipped (with rationale):

  • OX_PROJECT_ROOT rename → this is an existing internal constant defined in internal/config/env.go:8 (EnvProjectRoot = "OX_PROJECT_ROOT"), consumed by ResolveProjectRootOverride. It is not a customer-set variable; ox sets it in the generated hook command so it can locate the repo root without a cwd walk. Renaming it across the codebase changes a published (hook-file-visible) interface and requires human review per project policy — filed for follow-up.

Bonus fix (OpenCode — ox-8arr):

  • ox-adapter-opencode declared CapIncrementalReader but never wired ReadFromOffset into adapterruntime.Config — every hook-driven OpenCode incremental read was silently falling back to full re-read. One-line fix: ReadFromOffset: handleReadFromOffset added to the Config struct. Same file: DSN prefix applied to OpenCode's openDB for consistency.

@rsnodgrass
rsnodgrass merged commit a39dfe1 into main Jul 31, 2026
12 of 13 checks passed
@rsnodgrass
rsnodgrass deleted the ryan/ox-adapter-goose branch July 31, 2026 01:55

@coderabbitai coderabbitai Bot 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.

🧹 Nitpick comments (3)
cmd/ox-adapter-goose/hooks_test.go (1)

436-464: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Strengthen symlink tests: assert the redirect actually didn't happen.

Both tests only assert err != nil, so they'd pass for any unrelated install failure. Asserting the external target is unmodified (and/or that the error mentions the symlink) pins the actual protection.

💚 Example for `TestInstallHooks_RefusesSymlinkedFile`
 	_, err := handleInstallHooks(projectParams(root))
 	if err == nil {
 		t.Fatal("install must refuse to write through a symlinked plugin.json")
 	}
+	got, readErr := os.ReadFile(target)
+	if readErr != nil {
+		t.Fatalf("read target: %v", readErr)
+	}
+	if string(got) != `{}` {
+		t.Errorf("install wrote through the symlink: %s", got)
+	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/ox-adapter-goose/hooks_test.go` around lines 436 - 464, Strengthen the
symlink protection assertions in the affected install tests, especially
TestInstallHooks_RefusesSymlinkedFile: after handleInstallHooks returns an
error, read the external target and assert its contents remain unchanged, and
verify the error identifies the symlink where practical. Apply the same
redirect-prevention assertion to the symlinked .agents directory test, ensuring
unrelated install failures cannot satisfy the tests.
cmd/ox-adapter-goose/hooks.go (2)

150-160: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

Reject paths that escape scopeRoot before walking components.

If path is not under scopeRoot, filepath.Rel yields .. components, and filepath.Join(current, "..") normalizes upward — silently walking outside the intended boundary instead of failing. Current callers always pass pluginDir-derived paths, so this is defensive only.

🛡️ Proposed guard
 	rel, err := filepath.Rel(scopeRoot, path)
 	if err != nil {
 		return fmt.Errorf("cannot relativize %q from %q: %w", path, scopeRoot, err)
 	}
+	if rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) {
+		return fmt.Errorf("%q escapes scope root %q", path, scopeRoot)
+	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/ox-adapter-goose/hooks.go` around lines 150 - 160, Update
verifyNoSymlinks to reject paths outside scopeRoot immediately after
filepath.Rel returns, detecting a relative path equal to ".." or beginning with
".." plus a path separator. Return an error before iterating components;
preserve the existing component-walking behavior for paths within scopeRoot.

161-167: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use errors.Is(err, os.ErrNotExist) instead of os.IsNotExist(err).

Matches Go 1.13+ unwrapping semantics and the repo's error-inspection convention.

♻️ Proposed change
 		fi, err := os.Lstat(current)
 		if err != nil {
-			if os.IsNotExist(err) {
+			if errors.Is(err, os.ErrNotExist) {
 				return nil // path doesn't exist yet — nothing to follow
 			}
 			return fmt.Errorf("lstat %q: %w", current, err)
 		}

Requires importing errors.

Based on learnings: "prefer errors.Is(err, os.ErrNotExist) over os.IsNotExist(err)". As per coding guidelines: "Use errors.Is() and errors.As() for error inspection".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/ox-adapter-goose/hooks.go` around lines 161 - 167, In the path handling
logic around os.Lstat, import the errors package and replace os.IsNotExist(err)
with errors.Is(err, os.ErrNotExist), preserving the existing return behavior for
missing paths and other errors.

Sources: Coding guidelines, Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@cmd/ox-adapter-goose/hooks_test.go`:
- Around line 436-464: Strengthen the symlink protection assertions in the
affected install tests, especially TestInstallHooks_RefusesSymlinkedFile: after
handleInstallHooks returns an error, read the external target and assert its
contents remain unchanged, and verify the error identifies the symlink where
practical. Apply the same redirect-prevention assertion to the symlinked .agents
directory test, ensuring unrelated install failures cannot satisfy the tests.

In `@cmd/ox-adapter-goose/hooks.go`:
- Around line 150-160: Update verifyNoSymlinks to reject paths outside scopeRoot
immediately after filepath.Rel returns, detecting a relative path equal to ".."
or beginning with ".." plus a path separator. Return an error before iterating
components; preserve the existing component-walking behavior for paths within
scopeRoot.
- Around line 161-167: In the path handling logic around os.Lstat, import the
errors package and replace os.IsNotExist(err) with errors.Is(err,
os.ErrNotExist), preserving the existing return behavior for missing paths and
other errors.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: bacb1849-3c7c-40ab-8e89-b227a1c2c9a0

📥 Commits

Reviewing files that changed from the base of the PR and between 24f7385 and 7000eae.

📒 Files selected for processing (14)
  • .sageox/README.md
  • cmd/ox-adapter-goose/hooks.go
  • cmd/ox-adapter-goose/hooks_test.go
  • cmd/ox-adapter-goose/main.go
  • cmd/ox-adapter-goose/serve.go
  • cmd/ox-adapter-goose/session.go
  • cmd/ox-adapter-opencode/main.go
  • cmd/ox-adapter-opencode/session.go
  • cmd/ox/guides/agents-md.md
  • cmd/ox/guides/getting-started.md
  • cmd/ox/guides/murmur-vs-rule.md
  • cmd/ox/guides/team-rules.md
  • cmd/ox/init.go
  • cmd/ox/release_notes.md
🚧 Files skipped from review as they are similar to previous changes (9)
  • cmd/ox/guides/murmur-vs-rule.md
  • cmd/ox/guides/team-rules.md
  • cmd/ox/guides/getting-started.md
  • cmd/ox/guides/agents-md.md
  • cmd/ox/release_notes.md
  • .sageox/README.md
  • cmd/ox-adapter-goose/serve.go
  • cmd/ox-adapter-goose/session.go
  • cmd/ox/init.go

Comment on lines +345 to +347

if err := writeHooksFile(hooksPath, hf); err != nil {
return nil, fmt.Errorf("failed to write hooks.json: %w", err)

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 security Uninstall follows repository symlinks

Project-scope uninstall reads and rewrites hooks.json without validating that the repository-derived path is not a symlink. A repository can point .agents/plugins/sageox/hooks/hooks.json at an external writable file; when that file contains a non-ox rule that must be retained, uninstall removes the ox rule and rewrites the external target. Reject symlinked hook paths before reading or writing them during uninstall, including the final hooks.json component.

Artifacts

Executable Goose uninstall symlink reproduction script

  • Creates the isolated fixture, builds the adapter, runs its uninstall command, and asserts the external rewrite; takeaway: the reproduction is repeatable.

Goose hooks symlink fixture before uninstall

  • Shows the repository Goose hooks path linked to the external file containing retained and ox rules; takeaway: the required preconditions were established.

Actual Goose adapter uninstall after symlink fixture

  • Shows actual adapter success output and the external target rewritten to retain only the non-ox rule while the link remains; takeaway: the defect is reproduced.

Goose adapter build log

  • Shows the build command for the tested adapter binary exited successfully; takeaway: execution used code built from this checkout.

View artifacts

T-Rex Ran code and verified through T-Rex

Fix in Claude Code

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