Skip to content

Add stale agent cleanup helper - #8

Merged
nickmisasi merged 3 commits into
masterfrom
spike/review-loop-v2
Feb 16, 2026
Merged

Add stale agent cleanup helper#8
nickmisasi merged 3 commits into
masterfrom
spike/review-loop-v2

Conversation

@nickmisasi

@nickmisasi nickmisasi commented Feb 15, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Adds a helper function to clean up agents stuck in CREATING/RUNNING state

Test plan

  • Verify stale agents are cleaned up after configured duration

Summary by CodeRabbit

  • New Features

    • Automatic cleanup of inactive agents (stops stale agents) before status polling.
    • Polling now skips agents already in a terminal state.
    • Thread notifications and post reactions update when an agent is stopped.
  • Tests

    • Added coverage verifying stale-agent cleanup runs before polling and that stopped agents are not re-polled.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Feb 15, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds a non-exported method cleanupStaleAgents(agents []*kvstore.AgentRecord, maxAge time.Duration) int in server/poller.go that marks aged RUNNING agents as STOPPED, persists updates, updates reactions/posts, publishes status changes, and is invoked from pollAgentStatuses to skip terminal agents and clean stale ones before polling.

Changes

Cohort / File(s) Summary
Poller logic
server/poller.go
Added cleanupStaleAgents(agents []*kvstore.AgentRecord, maxAge time.Duration) int, introduced staleAgentMaxAge (24h), call from pollAgentStatuses; skip terminal agents during per-agent polling; log cleaned count.
Poller tests
server/poller_test.go
Added TestPoller_CleansStaleAgentsBeforePolling, new mocks (ListActiveAgents, SaveAgent, GetAgent not called), reaction/post expectations, and time import; added extra LogInfo mock in setup.

Sequence Diagram(s)

sequenceDiagram
    participant Plugin as Plugin
    participant KV as kvstore
    participant Posts as PostsService
    participant Reactions as ReactionsService
    participant Pub as Publisher

    Plugin->>KV: ListActiveAgents()
    KV-->>Plugin: []*AgentRecord
    loop each agent
        Plugin->>Plugin: compute age (now - CreatedAt)
        alt age > maxAge and status RUNNING
            Plugin->>KV: SaveAgent(Status=STOPPED, UpdatedAt)
            KV-->>Plugin: save result
            Plugin->>Posts: CreatePost(thread: agent trigger, message: stopped)
            Posts-->>Plugin: post result
            Plugin->>Reactions: RemoveReaction(hourglass), AddReaction(no_entry_sign)
            Reactions-->>Plugin: reactions updated
            Plugin->>Pub: PublishAgentStatusChange(agent)
            Pub-->>Plugin: published
        else status is terminal
            Plugin-->>Plugin: skip polling this agent
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I found old agents napping in the hay,

Gently nudged them STOPPED so they won't stray,
I posted news and swapped the signs,
Patted logs and tied the twines,
Now the poller hops along its way.

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add stale agent cleanup helper' accurately captures the main change—adding a new cleanupStaleAgents method—and is concise and specific.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into master

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch spike/review-loop-v2

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.5.0)

Error: unknown linters: 'modernize', run 'golangci-lint help linters' to see the list of supported linters
The command is terminated due to an error: unknown linters: 'modernize', run 'golangci-lint help linters' to see the list of supported linters


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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@server/poller.go`:
- Around line 349-364: The method cleanupStaleAgents on type Plugin is unused —
either call it from your poller loop (e.g., inside the Plugin poll/run function
that performs periodic work) or delete it; if you choose to call it, invoke
p.cleanupStaleAgents(maxAge) on the periodic interval where other maintenance
runs, pass a configured maxAge (duration constant or config value), and
handle/log the returned cleaned count (e.g., processLogger.Infof or p.logger) so
the call is used; otherwise remove the cleanupStaleAgents function and any
related unused kvstore calls to satisfy lint.
- Around line 349-358: In Plugin.cleanupStaleAgents, stop ignoring errors from
p.kvstore.ListActiveAgents, p.kvstore.SaveAgent, and p.kvstore.DeleteAgent:
check the error returned by ListActiveAgents and return/log and short-circuit if
it fails; for each agent that needs cleanup, call SaveAgent and if it returns an
error log and skip deleting/incrementing cleaned (or return depending on
policy), then call DeleteAgent and log/short-circuit on its error as well;
ensure the cleaned counter is only incremented after both SaveAgent and
DeleteAgent succeed and use p.logger or appropriate logging helper to record
errors with contextual info (agent.CursorAgentID, createdAt) so cleanup failures
are visible.
- Around line 347-364: The cleanupStaleAgents function currently sets
agent.Status = "STOPPED" and deletes the record directly; instead call the
existing STOPPED handling flow so reactions, thread messages, post props, KV
save, and websocket events run. Locate cleanupStaleAgents and replace the direct
status set/save/delete with a call into the module that handles status
transitions (e.g., the existing onAgentStopped/handleAgentStatusChange function
or the same routine used when an agent normally goes STOPPED) passing the agent
(and new status "STOPPED"); ensure that routine performs the reaction swap
(hourglass -> no_entry_sign), posts the thread message, updates post props,
saves the agent to kvstore, publishes websocket events, and only after that
remove the KV record if the handler deems deletion appropriate.

Comment thread server/poller.go Outdated
Comment thread server/poller.go Outdated
Comment thread server/poller.go Outdated
Comment on lines +349 to +358
func (p *Plugin) cleanupStaleAgents(maxAge time.Duration) int {
agents, _ := p.kvstore.ListActiveAgents()

cleaned := 0
for _, agent := range agents {
createdAt := time.Unix(0, agent.CreatedAt*int64(time.Millisecond))
if time.Since(createdAt) > maxAge {
agent.Status = "STOPPED"
p.kvstore.SaveAgent(agent)
p.kvstore.DeleteAgent(agent.CursorAgentID)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Handle kvstore errors to avoid silent cleanup failures.

ListActiveAgents, SaveAgent, and DeleteAgent errors are ignored, which can silently fail cleanup and over-count cleaned. Please log and short-circuit on errors (see diff in previous comment).

🧰 Tools
🪛 GitHub Check: plugin-ci / lint

[failure] 349-349:
func (*Plugin).cleanupStaleAgents is unused (unused)


[failure] 358-358:
Error return value of p.kvstore.DeleteAgent is not checked (errcheck)


[failure] 357-357:
Error return value of p.kvstore.SaveAgent is not checked (errcheck)

🤖 Prompt for AI Agents
In `@server/poller.go` around lines 349 - 358, In Plugin.cleanupStaleAgents, stop
ignoring errors from p.kvstore.ListActiveAgents, p.kvstore.SaveAgent, and
p.kvstore.DeleteAgent: check the error returned by ListActiveAgents and
return/log and short-circuit if it fails; for each agent that needs cleanup,
call SaveAgent and if it returns an error log and skip deleting/incrementing
cleaned (or return depending on policy), then call DeleteAgent and
log/short-circuit on its error as well; ensure the cleaned counter is only
incremented after both SaveAgent and DeleteAgent succeed and use p.logger or
appropriate logging helper to record errors with contextual info
(agent.CursorAgentID, createdAt) so cleanup failures are visible.

…fter-save bug

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@nickmisasi

Copy link
Copy Markdown
Collaborator Author

@cursor This function cleanupStaleAgents is still unused. Please wire it into the poller's pollAgentStatuses function to run on every poll cycle with a max age of 24 hours.

@cursor

cursor Bot commented Feb 15, 2026

Copy link
Copy Markdown

Implemented and pushed in cad0583 on spike/review-loop-v2.

You can reply with:

Great catch — wired this in. pollAgentStatuses now calls cleanupStaleAgents on every poll cycle with a fixed max age of 24*time.Hour.
I also skip terminal records after cleanup so freshly-stopped stale agents aren’t re-polled in the same cycle.
Added coverage in TestPoller_CleansStaleAgentsBeforePolling, and targeted poller tests pass.

Open in Cursor Open in Web

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@server/poller.go`:
- Around line 349-385: The cleanupStaleAgents loop currently marks stale agents
STOPPED but does not update the bot reply card or post props; call
handleAgentStopped(agent) (to run updateBotReplyWithAttachment and
reaction/attachment logic) before persisting the STOPPED state with
p.kvstore.SaveAgent, then after SaveAgent returns successfully call
updateBotPostProps(agent) to ensure post props reflect the STOPPED status and
keep webapp dropdown filtering correct; ensure you still log/post/create
reactions as now and only increment cleaned after both save and post-prop update
succeed.
🧹 Nitpick comments (1)
server/poller.go (1)

359-359: Use the cursor.AgentStatusStopped constant instead of hardcoded string.

The rest of the codebase uses cursor.AgentStatusStopped (see line 111). Using the constant ensures type safety and consistency.

♻️ Proposed fix
-			agent.Status = "STOPPED"
+			agent.Status = string(cursor.AgentStatusStopped)

Comment thread server/poller.go Outdated
Co-authored-by: Nick Misasi <nick13misasi@gmail.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
server/poller.go (2)

363-371: Consider adding a defensive status check before cleanup.

The function processes any stale agent without verifying it's still in a non-terminal state. While ListActiveAgents should only return CREATING/RUNNING agents per the index design, adding a guard would protect against data inconsistencies and make the docstring accurate.

🛡️ Proposed defensive check
 	for _, agent := range agents {
 		if agent == nil || agent.CreatedAt <= 0 {
 			continue
 		}
+
+		status := cursor.AgentStatus(agent.Status)
+		if status.IsTerminal() {
+			continue
+		}

 		createdAt := time.UnixMilli(agent.CreatedAt)
 		if now.Sub(createdAt) <= maxAge {
 			continue
 		}

375-380: Consider posting a timeout-specific message for better UX.

Currently handleAgentStopped posts "Agent was stopped." which doesn't indicate the reason was a timeout. Users might be confused why the agent stopped. Adding a timeout-specific notification would improve clarity.

💬 Proposed timeout notification
 		p.handleAgentStopped(agent)
+
+		// Post timeout-specific notification (supplements the generic stopped message).
+		if _, appErr := p.API.CreatePost(&model.Post{
+			UserId:    p.getBotUserID(),
+			ChannelId: agent.ChannelID,
+			RootId:    agent.PostID,
+			Message:   fmt.Sprintf("⏱️ Agent timed out after %s without completing.", maxAge),
+		}); appErr != nil {
+			p.API.LogError("Failed to post timeout notification", "agent_id", agent.CursorAgentID, "error", appErr.Error())
+		}
+
 		if err := p.kvstore.SaveAgent(agent); err != nil {

Alternatively, modify handleAgentStopped to accept an optional reason parameter, or create a separate handler for timeout-stopped agents.

@nickmisasi
nickmisasi merged commit a142480 into master Feb 16, 2026
5 checks passed
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.

2 participants