Skip to content

DOS-1595: park quota retries until reset window - #16

Draft
mdostal wants to merge 1 commit into
mainfrom
agent/dostal-dev/549dc5da
Draft

DOS-1595: park quota retries until reset window#16
mdostal wants to merge 1 commit into
mainfrom
agent/dostal-dev/549dc5da

Conversation

@mdostal

@mdostal mdostal commented Jul 23, 2026

Copy link
Copy Markdown

Closes DOS-1595

Summary

  • add queued_after support for agent task retry rows so parked quota retries are not claimable until release
  • classify provider quota exhaustion as retryable only through reset-window parking, with wait_reason and a single system comment
  • keep non-quota transient runtime retry behavior unchanged and update generated db call sites

Tests

  • go test ./internal/service ./pkg/taskfailure
  • go test ./... (fails against local test DB before migration: ERROR column queued_after does not exist; also observed pre-existing timing failures in pkg/agent Codex fixture tests)

Summary by CodeRabbit

  • New Features

    • Automatically retries tasks affected by provider quota limits.
    • Delays retries until the provider’s quota is expected to reset.
    • Adds retry status details explaining the delay and expected retry time.
  • Bug Fixes

    • Prevents deferred retry tasks from being claimed or run before their scheduled release time.
    • Preserves or refreshes session and working-directory details correctly during retries.

Co-authored-by: multica-agent <github@multica.ai>
@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Provider quota failures are now retryable with parsed or default backoff timing. Retry tasks persist wait_reason and queued_after, and claim queries defer them until eligible. Tests and retry-task call sites use the updated SQL parameter structure.

Changes

Provider quota retry flow

Layer / File(s) Summary
Deferred retry queue contract
server/migrations/121_agent_task_queue_queued_after.*.sql, server/pkg/db/queries/agent.sql, server/cmd/server/rerun_session_test.go, server/internal/handler/squad_comment_trigger_test.go
Adds nullable queued_after storage, persists retry scheduling fields, gates task claiming until release, and updates tests to pass CreateRetryTaskParams.
Quota-aware retry scheduling
server/internal/service/task.go
Classifies provider quota failures as retryable, parses reset timestamps or durations, applies a one-hour fallback, stores scheduling metadata, logs the release time, and posts an issue comment.
Classification and scheduling validation
server/internal/service/task_complete_race_test.go
Tests provider failure classifications, quota reset parsing, and queued_after guards in claim queries.

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

Sequence Diagram(s)

sequenceDiagram
  participant TaskService
  participant QuotaParser
  participant AgentTaskQueue
  participant Issue
  TaskService->>QuotaParser: Parse provider quota reset timing
  QuotaParser-->>TaskService: Return retry timestamp
  TaskService->>AgentTaskQueue: Create retry with wait_reason and queued_after
  TaskService->>Issue: Post retry scheduling comment
  AgentTaskQueue-->>TaskService: Claim task after queued_after
Loading

Possibly related PRs

Suggested reviewers: bohan-j, ldnvnbl

Poem

A bunny found a quota gate,
And tucked retries beneath its plate.
“Wait till the reset,” the task queue sighs,
While timestamps hop through moonlit skies.
An issue note says, “Soon you’ll run!”
Then carrots—and retries—are done.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: parking quota retries until the reset window.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch agent/dostal-dev/549dc5da

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.12.2)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies"


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.

@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

Caution

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

⚠️ Outside diff range comments (2)
server/internal/service/task.go (1)

1588-1595: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Logging a zero-value queued_after for non-quota retries.

child.QueuedAfter.Time is logged unconditionally; for non-quota retries QueuedAfter.Valid is false, so this logs the Go zero time (0001-01-01...), which is noisy/misleading in logs.

🧹 Optional cleanup
+	queuedAfter := interface{}(nil)
+	if child.QueuedAfter.Valid {
+		queuedAfter = child.QueuedAfter.Time
+	}
 	slog.Info("task auto-retry enqueued",
 		"parent_task_id", util.UUIDToString(parent.ID),
 		"child_task_id", util.UUIDToString(child.ID),
 		"reason", reason,
 		"attempt", child.Attempt,
 		"max_attempts", child.MaxAttempts,
-		"queued_after", child.QueuedAfter.Time,
+		"queued_after", queuedAfter,
 	)
🤖 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 `@server/internal/service/task.go` around lines 1588 - 1595, Update the “task
auto-retry enqueued” log to include queued_after only when
child.QueuedAfter.Valid is true; omit the field for non-quota retries instead of
logging child.QueuedAfter.Time’s zero value, while preserving the existing
fields and values.
server/pkg/db/queries/agent.sql (1)

482-522: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Exclude queued_after from ExpireStaleQueuedTasks.

queuedTTLSeconds is 2 hours, but CreateRetryTask can park provider-quota retries until a provider reset window that is longer than normal queue backlog. Rows with status = 'queued' and queued_after IS NULL OR queued_after <= now() are not claimable yet, so this sweeper fails them with failure_reason='queued_expired' instead of waiting for the reset window.

🩹 Proposed guard
 WITH victims AS (
     SELECT id FROM agent_task_queue
     WHERE status = 'queued'
       AND created_at < now() - make_interval(secs => `@ttl_secs`::double precision)
+      AND (queued_after IS NULL OR queued_after <= now())
     ORDER BY created_at ASC
     LIMIT `@max_per_tick`::int
     FOR UPDATE SKIP LOCKED
 )
🤖 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 `@server/pkg/db/queries/agent.sql` around lines 482 - 522, Update
ExpireStaleQueuedTasks so both the victims CTE and outer UPDATE exclude deferred
retries by requiring queued_after IS NULL OR queued_after > now(). Preserve
expiration for immediately claimable queued rows while leaving rows parked until
their provider reset window.
🤖 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 `@server/internal/service/task.go`:
- Around line 1443-1452: Update the parked quota-retry handling near retried and
createAgentComment so chat-session tasks also receive the parked-retry message
when IssueID is invalid. Reuse the existing formatted message and route it
through the chat-session history/messaging path, while preserving the
issue-comment path for tasks with a valid IssueID and ensuring users see at
least one failure message.

---

Outside diff comments:
In `@server/internal/service/task.go`:
- Around line 1588-1595: Update the “task auto-retry enqueued” log to include
queued_after only when child.QueuedAfter.Valid is true; omit the field for
non-quota retries instead of logging child.QueuedAfter.Time’s zero value, while
preserving the existing fields and values.

In `@server/pkg/db/queries/agent.sql`:
- Around line 482-522: Update ExpireStaleQueuedTasks so both the victims CTE and
outer UPDATE exclude deferred retries by requiring queued_after IS NULL OR
queued_after > now(). Preserve expiration for immediately claimable queued rows
while leaving rows parked until their provider reset window.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4e0638f8-495c-49eb-8e73-ee72e3f35baf

📥 Commits

Reviewing files that changed from the base of the PR and between 9696a37 and 71d8302.

⛔ Files ignored due to path filters (5)
  • server/pkg/db/generated/agent.sql.go is excluded by !**/generated/**
  • server/pkg/db/generated/autopilot.sql.go is excluded by !**/generated/**
  • server/pkg/db/generated/chat.sql.go is excluded by !**/generated/**
  • server/pkg/db/generated/models.go is excluded by !**/generated/**
  • server/pkg/db/generated/runtime.sql.go is excluded by !**/generated/**
📒 Files selected for processing (7)
  • server/cmd/server/rerun_session_test.go
  • server/internal/handler/squad_comment_trigger_test.go
  • server/internal/service/task.go
  • server/internal/service/task_complete_race_test.go
  • server/migrations/121_agent_task_queue_queued_after.down.sql
  • server/migrations/121_agent_task_queue_queued_after.up.sql
  • server/pkg/db/queries/agent.sql

Comment on lines +1443 to +1452
if retried != nil &&
failureReason == taskfailure.ReasonAgentProviderQuotaLimit.String() &&
task.IssueID.Valid &&
retried.QueuedAfter.Valid {
msg := fmt.Sprintf(
"Provider quota exhausted; parked retry until %s.",
retried.QueuedAfter.Time.UTC().Format(time.RFC3339),
)
s.createAgentComment(ctx, task.IssueID, task.AgentID, msg, "system", task.TriggerCommentID)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Locate task.go and relevant symbols"
fd -a 'task\.go$' . | sed 's#^\./##'

echo
echo "Extract relevant section and nearby comment/send logic"
if [ -f server/internal/service/task.go ]; then
  wc -l server/internal/service/task.go
  sed -n '1400,1505p' server/internal/service/task.go | cat -n -v | sed 's/^/  /'
else
  echo "server/internal/service/task.go not found"
fi

echo
printf '\nSearch for createAgentComment usages and ChatSessionID conditions around task retries\n'
rg -n "createAgentComment|ReasonAgentProviderQuotaLimit|RetryStatus|retried != nil|ChatSessionID|IssueID.Valid|TaskStatusRetry" server/internal/service/task.go server/internal -S

Repository: firefly-events/multica

Length of output: 30144


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Inspect maybe retry and retry condition around failed task handling"
sed -n '1500,1615p' server/internal/service/task.go | cat -n -v | sed 's/^/  /'

echo
echo "Find MaybeRetryFailedTask implementation and its return shape"
rg -n "func .*MaybeRetryFailedTask|RetryTask|MaybeRetry|New.*Task|QueuedAfter" server/internal/service/task.go server/internal -S

echo
echo "Focused searches for MaybeRetryFailedTask"
for f in $(rg -l "MaybeRetryFailedTask" server); do
  echo "--- $f ---"
  wc -l "$f"
  ast-grep outline "$f" --match MaybeRetryFailedTask --view expanded || true
  rg -n -C 4 "MaybeRetryFailedTask" "$f"
done

Repository: firefly-events/multica

Length of output: 14386


Add a chat-session equivalent for parked quota retries.

When a chat-session task is retried for agent_provider_quota_limit, retried is not nil so the chat history mirror is skipped; because IssueID is not valid, the system comment path is also skipped. Either surface the same parked-retry message to chat sessions or change the suppression so the user sees at least one failure message.

🤖 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 `@server/internal/service/task.go` around lines 1443 - 1452, Update the parked
quota-retry handling near retried and createAgentComment so chat-session tasks
also receive the parked-retry message when IssueID is invalid. Reuse the
existing formatted message and route it through the chat-session
history/messaging path, while preserving the issue-comment path for tasks with a
valid IssueID and ensuring users see at least one failure message.

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