Skip to content

add session abort command and doctor recovery options#23

Merged
rsnodgrass merged 3 commits intomainfrom
ryan/session-abort
Feb 20, 2026
Merged

add session abort command and doctor recovery options#23
rsnodgrass merged 3 commits intomainfrom
ryan/session-abort

Conversation

@rsnodgrass
Copy link
Copy Markdown
Contributor

@rsnodgrass rsnodgrass commented Feb 20, 2026

Summary

Add session abort capability for users to discard active sessions without uploading to the ledger, plus improved recovery confirmation prompts. Addresses the need for users to control when sessions are published.

New feature: ox agent <id> session abort --force discards the active session, clearing all local session data. Interactive terminals get a y/N confirmation prompt; non-interactive callers (agents/pipes) require --force.

Improved doctor: When orphaned/stale sessions are detected (>24h old), doctor now suggests both recovery and abort options, giving users explicit control.

Testing: 5 focused tests cover the abort lifecycle, non-interactive forced flow, empty-path safety guard, and confirmation behavior.

Test Plan

  • All 4689 tests pass, 0 lint issues
  • Abort tests verify session data removal, recording state cleanup, and safety guards
  • Doctor correctly suggests recovery + abort options for stale sessions

Summary by CodeRabbit

Release Notes

  • New Features

    • Added session abort command to discard active session data locally without uploading to ledger
    • Enforces destructive confirmation: interactive mode prompts user; non-interactive mode requires --force flag
    • Includes safety checks to prevent accidental deletion of critical paths
    • Doctor output now recommends abort for stale sessions
  • Documentation

    • Added documentation for session abort command
  • Tests

    • Added comprehensive test coverage for abort functionality including edge cases

@rsnodgrass rsnodgrass marked this pull request as ready for review February 20, 2026 22:44
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Feb 20, 2026

Warning

Rate limit exceeded

@rsnodgrass has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 2 minutes and 17 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📝 Walkthrough

Walkthrough

Introduces a new session abort command to destructively terminate active recording sessions without uploading to the ledger, including command routing, confirmation enforcement, and integration with health checks.

Changes

Cohort / File(s) Summary
Documentation
.claude/commands/ox-session-abort.md, extensions/claude/commands/ox-session-abort.md
Adds documentation for the new ox-session-abort command, describing its destructive nature, irreversibility, and example invocation.
Command Routing
cmd/ox/agent.go
Registers new "abort" session subcommand and updates help/usage text and unknown command messaging.
Health Check Integration
cmd/ox/agent_doctor.go, internal/doctor/session.go
Detects orphaned or stale recordings (>24 hours old) and surfaces abort/recovery options in doctor output. Updates fix message text to recommend abort --force for discarding sessions.
Session Abort Implementation
cmd/ox/agent_session_abort.go, cmd/ox/agent_session_abort_test.go
Implements abort handler with destructive confirmation logic (--force flag required in non-interactive mode), session state clearing, directory removal safeguards, and JSON output. Includes five test cases covering non-recording state, state clearing, directory removal, safety against path corruption, and force flag enforcement.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant CLI as CLI Handler
    participant State as Recording State
    participant FS as File System
    participant Output as JSON Output

    User->>CLI: ox agent <id> session abort [--force]
    CLI->>State: Load recording state
    alt Recording is active
        alt Non-interactive mode
            alt --force flag present
                CLI->>State: Clear recording state
                CLI->>FS: Remove session directory
                FS-->>CLI: Directory removed
                CLI->>Output: Generate success result
                Output-->>User: JSON output + session aborted
            else --force flag missing
                CLI-->>User: Error: destructive action requires --force
            end
        else Interactive mode
            CLI->>User: Prompt for confirmation
            User-->>CLI: Confirm/Deny
            alt User confirms
                CLI->>State: Clear recording state
                CLI->>FS: Remove session directory
                FS-->>CLI: Directory removed
                CLI->>Output: Generate success result
                Output-->>User: JSON output + session aborted
            else User denies
                CLI-->>User: Abort cancelled
            end
        end
    else Recording is inactive
        CLI-->>User: Error: no active session
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A session abort, how fine and neat,
With safety checks to guard the fleet,
The --force flag stands proud and tall,
Discarding data, answering the call,
No ledger trails, just cleaned-up ground! 🌱

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: adding a session abort command and enhancing doctor recovery options.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch ryan/session-abort

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
cmd/ox/agent_doctor.go (1)

97-117: ⚠️ Potential issue | 🔴 Critical

output.NextSteps for the orphaned session is silently discarded.

The orphaned-session step appended at lines 103–105 is immediately overwritten at line 117 because buildNextSteps creates a fresh var steps []string and never reads output.NextSteps. The new doctor guidance will never appear in JSON output.

🐛 Proposed fix
-	// build next steps based on state
-	output.NextSteps = buildNextSteps(output)
+	// build next steps based on state, preserving any steps already appended above
+	output.NextSteps = append(output.NextSteps, buildNextSteps(output)...)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/ox/agent_doctor.go` around lines 97 - 117, The orphaned-session message
appended to output.NextSteps is being overwritten because buildNextSteps(output)
builds a fresh steps slice and ignores output.NextSteps; update the logic so
existing steps are preserved: either modify buildNextSteps (function
buildNextSteps) to accept and merge any pre-existing output.NextSteps (or read
output.NextSteps inside it) or, after calling buildNextSteps, append its
returned steps to the already-populated output.NextSteps instead of replacing
them; make the change where output.NextSteps is set/returned so the orphaned
session message from session.IsRecording/session.LoadRecordingState is retained
in the final output.
🧹 Nitpick comments (3)
cmd/ox/agent_session_abort.go (2)

13-20: sessionAbortOutput is missing a guidance field.

Per coding guidelines, agent behavioral guidance belongs in CLI JSON output under a guidance field, not in agent-specific skill files.

♻️ Suggested addition
 type sessionAbortOutput struct {
 	Success     bool   `json:"success"`
 	Type        string `json:"type"`
 	AgentID     string `json:"agent_id"`
 	SessionName string `json:"session_name,omitempty"`
 	Message     string `json:"message"`
+	Guidance    string `json:"guidance,omitempty"`
 }

Then set it when constructing the output:

 output := sessionAbortOutput{
 	Success:     true,
 	Type:        "session_abort",
 	AgentID:     inst.AgentID,
 	SessionName: sessionName,
 	Message:     "session aborted and discarded",
+	Guidance:    "Session data has been permanently deleted and was not uploaded to the ledger. Run 'ox agent prime' to start a new session.",
 }

As per coding guidelines: "Place agent behavioral guidance in CLI JSON output (e.g., guidance field), not in agent-specific skill files—skills should be thin wrappers relaying CLI output."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/ox/agent_session_abort.go` around lines 13 - 20, The sessionAbortOutput
struct is missing the guidance field required by CLI JSON output; add a Guidance
string `guidance` field to the sessionAbortOutput type and ensure any code that
constructs or returns sessionAbortOutput (e.g., where sessionAbortOutput
instances are created for abort responses) sets the Guidance value with the
agent behavioral guidance text so the CLI emits guidance in JSON rather than
embedding it in agent skill files.

99-106: hasFlag won't match --force=true syntax.

hasFlag does exact string comparison, so --force=true (a valid boolean flag form in some shells/tools) won't be recognized as --force. Since the only documented form is --force and this is an agent-facing command, this is low-risk, but worth noting if cobra's pflag-style parsing is used elsewhere.

♻️ More robust alternative using `strings.HasPrefix`
 func hasFlag(args []string, flag string) bool {
 	for _, arg := range args {
-		if arg == flag {
+		if arg == flag || strings.HasPrefix(arg, flag+"=") {
 			return true
 		}
 	}
 	return false
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/ox/agent_session_abort.go` around lines 99 - 106, The hasFlag function
currently does exact string equality and will miss forms like "--force=true";
update hasFlag (function name: hasFlag) to treat an argument as a match if it
equals the flag OR if it has the flag followed by '=' (e.g., use
strings.HasPrefix to check arg == flag || strings.HasPrefix(arg, flag+"=")) so
both "--force" and "--force=true" are recognized; ensure you import "strings" if
not already present.
cmd/ox/agent_session_abort_test.go (1)

43-115: Tests should be table-driven per coding guidelines.

The five tests cover distinct sub-scenarios (no recording, state cleared, folder removed, corrupt path, force required) that are well-suited to a table-driven structure. As per coding guidelines, test files should use table-driven tests.

As per coding guidelines, "**/*_test.go: Use table-driven tests and test error paths in Go test files".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmd/ox/agent_session_abort_test.go` around lines 43 - 115, The five separate
tests (TestAbortNotRecording, TestAbortClearsRecordingState,
TestAbortRemovesSessionFolder, TestAbortEmptySessionPathDoesNotDeleteCwd,
TestAbortRequiresForce) should be consolidated into a single table-driven test
named e.g. TestAbort with a slice of cases describing the scenario name, setup
function or flags, expected error/side-effects, and any pre/post checks; inside
the loop call t.Run(case.name, func(t *testing.T){ ... }) and invoke existing
helpers (setupSessionTestProject, setupAbortTest, runAgentSessionAbort,
session.IsRecording) per-case, performing the same assertions (error
contains/NoError, IsRecording false, session folder removed via os.Stat,
projectRoot exists for corrupt SessionPath, and cli.SetNoInteractive toggling
for the "requires force" case) so behavior is preserved while following
table-driven test guidelines.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.claude/commands/ox-session-abort.md:
- Line 4: The example command currently shows "$ox agent session abort --force"
which omits the required <agent_id> and will fail unless SAGEOX_AGENT_ID is set;
update the example to the canonical form "ox agent <agent_id> session abort
--force" (or show both forms and add a short note about SAGEOX_AGENT_ID) so
users can copy-paste a working command; modify the example string in the
ox-session-abort command doc and include the <agent_id> placeholder (and
optionally a one-line note about the env var) to prevent the "no agent ID"
error.

In `@cmd/ox/agent_session_abort_test.go`:
- Around line 85-102: The tests in this file (including
TestAbortEmptySessionPathDoesNotDeleteCwd and TestAbortNotRecording) should be
converted to a table-driven style: create a slice of test cases with names and
input setup, loop over them with t.Run and reuse the shared setup/teardown logic
(setupAbortTest, corruptState steps, and runAgentSessionAbort) to avoid
duplicated code. Replace manual defer os.Chdir(origDir) and any other
defer-based teardowns with t.Cleanup(...) calls so cleanup is consistent across
all cases, and ensure you capture and handle the error returned by os.Getwd()
(or require.NoError/ t.Fatalf when obtaining origDir) instead of ignoring it.
Keep assertions (e.g., os.Stat(projectRoot) check) per-case inside the table
loop so each subtest validates its own state.

---

Outside diff comments:
In `@cmd/ox/agent_doctor.go`:
- Around line 97-117: The orphaned-session message appended to output.NextSteps
is being overwritten because buildNextSteps(output) builds a fresh steps slice
and ignores output.NextSteps; update the logic so existing steps are preserved:
either modify buildNextSteps (function buildNextSteps) to accept and merge any
pre-existing output.NextSteps (or read output.NextSteps inside it) or, after
calling buildNextSteps, append its returned steps to the already-populated
output.NextSteps instead of replacing them; make the change where
output.NextSteps is set/returned so the orphaned session message from
session.IsRecording/session.LoadRecordingState is retained in the final output.

---

Duplicate comments:
In `@extensions/claude/commands/ox-session-abort.md`:
- Line 4: The documentation example for the CLI command is missing the required
agent identifier; update the usage and examples for the "ox agent session abort"
entry (ox-session-abort.md) to include the positional <agent_id> argument (e.g.
use "$ox agent session abort <agent_id> --force"), and ensure any surrounding
help text or examples mention and describe the <agent_id> parameter so callers
know it is required.

---

Nitpick comments:
In `@cmd/ox/agent_session_abort_test.go`:
- Around line 43-115: The five separate tests (TestAbortNotRecording,
TestAbortClearsRecordingState, TestAbortRemovesSessionFolder,
TestAbortEmptySessionPathDoesNotDeleteCwd, TestAbortRequiresForce) should be
consolidated into a single table-driven test named e.g. TestAbort with a slice
of cases describing the scenario name, setup function or flags, expected
error/side-effects, and any pre/post checks; inside the loop call
t.Run(case.name, func(t *testing.T){ ... }) and invoke existing helpers
(setupSessionTestProject, setupAbortTest, runAgentSessionAbort,
session.IsRecording) per-case, performing the same assertions (error
contains/NoError, IsRecording false, session folder removed via os.Stat,
projectRoot exists for corrupt SessionPath, and cli.SetNoInteractive toggling
for the "requires force" case) so behavior is preserved while following
table-driven test guidelines.

In `@cmd/ox/agent_session_abort.go`:
- Around line 13-20: The sessionAbortOutput struct is missing the guidance field
required by CLI JSON output; add a Guidance string `guidance` field to the
sessionAbortOutput type and ensure any code that constructs or returns
sessionAbortOutput (e.g., where sessionAbortOutput instances are created for
abort responses) sets the Guidance value with the agent behavioral guidance text
so the CLI emits guidance in JSON rather than embedding it in agent skill files.
- Around line 99-106: The hasFlag function currently does exact string equality
and will miss forms like "--force=true"; update hasFlag (function name: hasFlag)
to treat an argument as a match if it equals the flag OR if it has the flag
followed by '=' (e.g., use strings.HasPrefix to check arg == flag ||
strings.HasPrefix(arg, flag+"=")) so both "--force" and "--force=true" are
recognized; ensure you import "strings" if not already present.

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