-
Notifications
You must be signed in to change notification settings - Fork 0
feat(daemon): send Anthropic usage data to server #241
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -432,6 +432,9 @@ func (s *CCInfoTimerService) fetchRateLimit(ctx context.Context) { | |
| s.rateLimitCache.fetchedAt = time.Now() | ||
| s.rateLimitCache.mu.Unlock() | ||
|
|
||
| // Send usage data to server for push notification scheduling (fire-and-forget) | ||
| go s.sendAnthropicUsageToServer(ctx, usage) | ||
|
|
||
| slog.Debug("Anthropic rate limit updated", | ||
| slog.Float64("5h", usage.FiveHourUtilization), | ||
| slog.Float64("7d", usage.SevenDayUtilization)) | ||
|
|
@@ -472,6 +475,49 @@ func (s *CCInfoTimerService) fetchUserProfile(ctx context.Context) { | |
| slog.Debug("User profile fetched", slog.String("login", profile.FetchUser.Login)) | ||
| } | ||
|
|
||
| // sendAnthropicUsageToServer sends the Anthropic usage data to the ShellTime server | ||
| // for scheduling push notifications when rate limits reset. | ||
| func (s *CCInfoTimerService) sendAnthropicUsageToServer(ctx context.Context, usage *AnthropicRateLimitData) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The function |
||
| if s.config.Token == "" { | ||
| return | ||
| } | ||
|
|
||
| type usageBucket struct { | ||
| Utilization float64 `json:"utilization"` | ||
| ResetsAt string `json:"resets_at"` | ||
| } | ||
| type usagePayload struct { | ||
| FiveHour usageBucket `json:"five_hour"` | ||
| SevenDay usageBucket `json:"seven_day"` | ||
| } | ||
|
|
||
| payload := usagePayload{ | ||
| FiveHour: usageBucket{ | ||
| Utilization: usage.FiveHourUtilization, | ||
| ResetsAt: usage.FiveHourResetsAt, | ||
| }, | ||
| SevenDay: usageBucket{ | ||
| Utilization: usage.SevenDayUtilization, | ||
| ResetsAt: usage.SevenDayResetsAt, | ||
| }, | ||
| } | ||
|
|
||
| err := model.SendHTTPRequestJSON(model.HTTPRequestOptions[usagePayload, any]{ | ||
| Context: ctx, | ||
| Endpoint: model.Endpoint{ | ||
| Token: s.config.Token, | ||
| APIEndpoint: s.config.APIEndpoint, | ||
| }, | ||
| Method: "POST", | ||
| Path: "/api/v1/anthropic-usage", | ||
| Payload: payload, | ||
| Timeout: 5 * time.Second, | ||
| }) | ||
| if err != nil { | ||
| slog.Warn("Failed to send anthropic usage to server", slog.Any("err", err)) | ||
| } | ||
| } | ||
|
Comment on lines
+478
to
+519
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider adding more robust error handling. While the current implementation logs a warning if sending usage data fails, it might be beneficial to implement a retry mechanism with exponential backoff to handle transient network issues. This would improve the reliability of sending usage data to the server. |
||
|
|
||
| // GetCachedRateLimit returns a copy of the cached rate limit data, or nil if not available. | ||
| func (s *CCInfoTimerService) GetCachedRateLimit() *AnthropicRateLimitData { | ||
| s.rateLimitCache.mu.RLock() | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔴 Fire-and-forget goroutine uses a context that is cancelled immediately after spawning
The
sendAnthropicUsageToServergoroutine is spawned at line 436 with the samectxthat was passed tofetchRateLimit. However, the callers offetchRateLimit(at lines 171-173 and 194-196) create a timeout context withdefer cancel()in the enclosing anonymous function. WhenfetchRateLimitreturns synchronously,cancel()fires, cancelling the context. The newly spawned goroutine then tries to make an HTTP request with this already-cancelled context, which will fail immediately.Root Cause and Impact
The call chain is:
timerLoopspawns an anonymous goroutine (line 166-174)ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)thendefer cancel()s.fetchRateLimit(ctx)is called synchronouslyfetchRateLimit, at line 436:go s.sendAnthropicUsageToServer(ctx, usage)— this spawns a goroutine with the samectxfetchRateLimitreturns →defer cancel()fires →ctxis cancelledsendAnthropicUsageToServercallsmodel.SendHTTPRequestJSONwhich useshttp.NewRequestWithContext(ctx, ...)atmodel/api.base.go:50— this request will fail immediately withcontext canceledImpact: The usage data will never be successfully sent to the server. The fire-and-forget feature is completely non-functional. Every attempt will log
"Failed to send anthropic usage to server"with a context cancellation error.Was this helpful? React with 👍 or 👎 to provide feedback.