Skip to content

revert(watcher): back out #138 OOM/shutdown-drain fixes; always fix Sentry - #145

Merged
ArnabChatterjee20k merged 13 commits into
mainfrom
revert-oom-fix
Sep 4, 2026
Merged

revert(watcher): back out #138 OOM/shutdown-drain fixes; always fix Sentry#145
ArnabChatterjee20k merged 13 commits into
mainfrom
revert-oom-fix

Conversation

@ArnabChatterjee20k

@ArnabChatterjee20k ArnabChatterjee20k commented Sep 4, 2026

Copy link
Copy Markdown
Member

What does this PR do?

Backs out the watcher join-handle-leak / shutdown-drain work that shipped inside #138 (branch fix/DISCORD-15363517-watcher-join-handle-leak), because that change is degrading the running daemon in production. Only those OOM-fix commits are reverted. Every other feature #138 carried is kept untouched: agent instructions (V10) and the dashboard instruction editors, routing_intent (V11), PR-review comment handling, intent classification, red-green enforcement, the resource monitor script, and Sentry-as-error classification.

The 11 reverted commits:

  • reap finished issue-processing task handles
  • close shutdown race recording spawned tasks
  • make shutdown drain cancel-safe
  • drain to completion instead of a fixed budget
  • bound shutdown drain, abort stragglers
  • track retry/review processing in spawn_handles
  • mark one-shot trigger watchers running
  • bound the post-abort join in shutdown drain
  • bound tokio runtime teardown on shutdown
  • make harness watcher Arc and mark it running (e2e)
  • skip finished handles in post-abort drain join

It also adds one change: Sentry issues now always route to the fix pipeline instead of deflecting to a "share repro steps" reply. A Sentry event already ships a stacktrace that pinpoints the defect, so an inability to reproduce it at runtime should not stop us from fixing it. In production this deflection path stranded ~55% of Sentry traffic (77 of 141 issues got a repro-steps comment and no PR).

Test Plan

  • cargo build passes on the full workspace.
  • No database migration change. The reverted code path does not touch schema; V10/V11 stay applied and the migrator only runs migrations above MAX(version), so a prod DB already at V11 is unaffected.
  • After deploy, watch daemon memory on the droplet. Reverting the shutdown-drain fixes reintroduces the join-handle leak the PR was addressing, so if the daemon restarts often, keep an eye on JoinHandle growth over time.

Related PRs and Issues

@ArnabChatterjee20k ArnabChatterjee20k changed the title Revert oom fix revert(watcher): back out #138 OOM/shutdown-drain fixes; always fix Sentry Sep 4, 2026
@greptile-apps

greptile-apps Bot commented Sep 4, 2026

Copy link
Copy Markdown

Greptile Summary

This PR reverts watcher task-lifecycle and bounded-shutdown changes from #138 while retaining its other features, and routes Sentry reports into the fix pipeline even when runtime verification cannot reproduce them.

  • Restores active-counter-based graceful draining and default Tokio runtime teardown.
  • Restores inline retry and review-trigger processing.
  • Removes normal-operation cleanup of completed issue-processing handles.
  • Sends all Sentry verification verdicts into the fix run.
  • No substantive behavior changed since the previous review.

Confidence Score: 0/5

The PR does not appear safe to merge because all five previously reported blocking lifecycle and diagnosis defects remain outstanding.

Completed polling-task handles still accumulate in spawn_handles without normal-operation reaping. Dispatch still checks shutdown state separately from spawning and recording its handle, allowing shutdown to miss a newly starting task. The active-counter drain can still lose completion notifications and consume its full timeout after work has finished. Non-reproduced Sentry verdicts still enter build_diagnosis_context, which claims independent reproduction. Default Tokio runtime destruction can still wait indefinitely for non-preemptible blocking work after the watcher’s drain returns. The only change since the previous review is formatting, so none of these outstanding findings was addressed.

Files Needing Attention: crates/claudear-engine/src/watcher.rs, crates/claudear-engine/src/processing.rs, src/main.rs

Important Files Changed

Filename Overview
crates/claudear-engine/src/processing.rs Routes every Sentry report into the fix pipeline, but still labels non-reproduced telemetry as independently reproduced.
crates/claudear-engine/src/watcher.rs Reverts handle reaping and robust task draining, leaving unbounded completed-handle retention and shutdown races.
src/main.rs Removes bounded Tokio runtime teardown, allowing blocking work to prevent process termination.
tests/e2e_real_repo.rs Reverts the harness watcher ownership and running-state setup to match the restored synchronous trigger contract.

Reviews (2): Last reviewed commit: "linting" | Re-trigger Greptile

.process_issue(source_clone, issue, match_result, None, None, intent)
.await;
});
self.spawn_handles.lock().await.push(handle);

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 Completed Handles Accumulate Indefinitely

Every polling dispatch adds its JoinHandle to spawn_handles, but completed handles are no longer reaped during normal daemon operation. Because the watcher remains alive and repeatedly polls for work, sustained issue processing retains one completed task allocation per issue. Memory usage can therefore grow without bound until the service is OOM-killed.

Knowledge Base Used:

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/claudear-engine/src/watcher.rs
Line: 3554

Comment:
**Completed Handles Accumulate Indefinitely**

Every polling dispatch adds its `JoinHandle` to `spawn_handles`, but completed handles are no longer reaped during normal daemon operation. Because the watcher remains alive and repeatedly polls for work, sustained issue processing retains one completed task allocation per issue. Memory usage can therefore grow without bound until the service is OOM-killed.

**Knowledge Base Used:**
- [AI review execution](https://app.greptile.com/appwrite/-/custom-context/knowledge-base/appwrite/claudear/-/docs/ai-review-execution.md)
- [Configuration and runtime operations](https://app.greptile.com/appwrite/-/custom-context/knowledge-base/appwrite/claudear/-/docs/configuration-and-operations.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code Fix in Codex

Comment on lines +3549 to +3554
let handle = tokio::spawn(async move {
watcher
.process_issue(source_clone, issue, match_result, None, None, intent)
.await;
});
self.spawn_handles.lock().await.push(handle);

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 Shutdown Can Miss New Tasks

Dispatch checks is_running before waiting for capacity, then spawns without rechecking it while registering the task. If shutdown starts after the check but before process_issue increments active_processing, stop_and_drain can see zero and return while the task is still starting. Runtime teardown may then cancel repository or attempt-state work even though shutdown was reported as graceful.

Knowledge Base Used:

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/claudear-engine/src/watcher.rs
Line: 3549-3554

Comment:
**Shutdown Can Miss New Tasks**

Dispatch checks `is_running` before waiting for capacity, then spawns without rechecking it while registering the task. If shutdown starts after the check but before `process_issue` increments `active_processing`, `stop_and_drain` can see zero and return while the task is still starting. Runtime teardown may then cancel repository or attempt-state work even though shutdown was reported as graceful.

**Knowledge Base Used:**
- [AI review execution](https://app.greptile.com/appwrite/-/custom-context/knowledge-base/appwrite/claudear/-/docs/ai-review-execution.md)
- [Configuration and runtime operations](https://app.greptile.com/appwrite/-/custom-context/knowledge-base/appwrite/claudear/-/docs/configuration-and-operations.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code Fix in Codex

Comment on lines +1159 to +1174
while self.active_processing.load(Ordering::SeqCst) > 0 {
if start.elapsed() > max_wait {
tracing::warn!(
remaining = self.active_processing.load(Ordering::SeqCst),
"Graceful shutdown timeout reached, some tasks may not have completed"
);
break;
}
})
.await;

if joined.is_ok() {
tracing::info!("Claude Watcher stopped gracefully");
return;
tracing::info!(
active_count = self.active_processing.load(Ordering::SeqCst),
"Waiting for active tasks to complete..."
);
// Wait for a task to finish (notifies via slot_available) or fall back
// to a periodic check in case the notification was missed.
let remaining = max_wait.saturating_sub(start.elapsed());
let _ = tokio::time::timeout(remaining, self.slot_available.notified()).await;

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 Drain Can Miss Completion Wakeups

The drain checks active_processing before registering the Notify waiter. If the final task decrements the counter and notifies between those operations, the wakeup is lost; approval-related early exits also decrement without notifying. Shutdown then waits for the full 30-second budget despite having no active work, which can exceed deployment termination windows.

Knowledge Base Used:

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/claudear-engine/src/watcher.rs
Line: 1159-1174

Comment:
**Drain Can Miss Completion Wakeups**

The drain checks `active_processing` before registering the `Notify` waiter. If the final task decrements the counter and notifies between those operations, the wakeup is lost; approval-related early exits also decrement without notifying. Shutdown then waits for the full 30-second budget despite having no active work, which can exceed deployment termination windows.

**Knowledge Base Used:**
- [AI review execution](https://app.greptile.com/appwrite/-/custom-context/knowledge-base/appwrite/claudear/-/docs/ai-review-execution.md)
- [Configuration and runtime operations](https://app.greptile.com/appwrite/-/custom-context/knowledge-base/appwrite/claudear/-/docs/configuration-and-operations.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code Fix in Codex

let always_fix = input.issue.source == "sentry";
if verdict.reproduced || always_fix {
// Carry the diagnosis into the fix run.
input.diagnosis = Some(verdict);

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 Unverified Diagnosis Claims Reproduction

A Sentry verdict with reproduced == false is now forwarded as the fix diagnosis, but build_diagnosis_context unconditionally tells the agent that the issue was independently reproduced and must not be questioned. When such a verdict contains useful details, the fix run receives factually incorrect instructions, increasing the risk of an unsupported change. The diagnosis text needs to distinguish telemetry evidence from successful reproduction.

Knowledge Base Used:

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/claudear-engine/src/processing.rs
Line: 2435

Comment:
**Unverified Diagnosis Claims Reproduction**

A Sentry verdict with `reproduced == false` is now forwarded as the fix diagnosis, but `build_diagnosis_context` unconditionally tells the agent that the issue was independently reproduced and must not be questioned. When such a verdict contains useful details, the fix run receives factually incorrect instructions, increasing the risk of an unsupported change. The diagnosis text needs to distinguish telemetry evidence from successful reproduction.

**Knowledge Base Used:**
- [AI review execution](https://app.greptile.com/appwrite/-/custom-context/knowledge-base/appwrite/claudear/-/docs/ai-review-execution.md)
- [Automated review workflows](https://app.greptile.com/appwrite/-/custom-context/knowledge-base/appwrite/claudear/-/docs/review-automation.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code Fix in Codex

Comment thread src/main.rs
Comment on lines 1695 to +1698
scope.set_tag("app.component", "claudear-backend");
});

let runtime = tokio::runtime::Builder::new_multi_thread()
tokio::runtime::Builder::new_multi_thread()

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 Runtime Teardown Can Hang

Removing the explicit Runtime::shutdown_timeout restores unbounded default runtime teardown. If the watcher's 30-second drain gives up while work remains in a non-preemptible blocking operation, dropping the Tokio runtime can keep waiting indefinitely. A normal SIGTERM or redeploy may therefore never terminate the process.

Knowledge Base Used:

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/main.rs
Line: 1695-1698

Comment:
**Runtime Teardown Can Hang**

Removing the explicit `Runtime::shutdown_timeout` restores unbounded default runtime teardown. If the watcher's 30-second drain gives up while work remains in a non-preemptible blocking operation, dropping the Tokio runtime can keep waiting indefinitely. A normal SIGTERM or redeploy may therefore never terminate the process.

**Knowledge Base Used:**
- [AI review execution](https://app.greptile.com/appwrite/-/custom-context/knowledge-base/appwrite/claudear/-/docs/ai-review-execution.md)
- [Configuration and runtime operations](https://app.greptile.com/appwrite/-/custom-context/knowledge-base/appwrite/claudear/-/docs/configuration-and-operations.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Claude Code Fix in Codex

@ArnabChatterjee20k

Copy link
Copy Markdown
Member Author

@greptile I am reverting it cause it was making the processing to much slow due to the synchronous dependency and anyways it wasn't protecting it from killing

@greptile-apps

greptile-apps Bot commented Sep 4, 2026

Copy link
Copy Markdown

That makes sense. If the fix serialized processing behind a synchronous dependency, the throughput regression is a valid reason to revert it. Also, the shutdown drain could only bound graceful shutdown; it could not prevent an OOM/SIGKILL, and blocking work would remain non-preemptible anyway.

The tradeoff is that reverting reintroduces join-handle retention and potentially slower runtime teardown, so monitoring handle/RSS growth after deployment is the right mitigation. The Sentry always-fix change is independent and should remain.

@ArnabChatterjee20k
ArnabChatterjee20k merged commit 36494c5 into main Sep 4, 2026
8 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.

1 participant