This is an automated email from the ASF dual-hosted git repository.
klesh pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/devlake.git
The following commit(s) were added to refs/heads/main by this push:
new 6535c778b fix(claude_code): resume incremental collection from the
last collected day (#9290)
6535c778b is described below
commit 6535c778b059a30bb847ea998e99fbaaa620dc5c
Author: Poul J. Clementsen <[email protected]>
AuthorDate: Sun Nov 1 04:37:13 2026 +0100
fix(claude_code): resume incremental collection from the last collected day
(#9290)
---
.../plugins/claude_code/tasks/collector_utils.go | 7 ++-
.../claude_code/tasks/collector_utils_test.go | 51 ++++++++++++++++++++--
2 files changed, 53 insertions(+), 5 deletions(-)
diff --git a/backend/plugins/claude_code/tasks/collector_utils.go
b/backend/plugins/claude_code/tasks/collector_utils.go
index fbf52764e..9aa191640 100644
--- a/backend/plugins/claude_code/tasks/collector_utils.go
+++ b/backend/plugins/claude_code/tasks/collector_utils.go
@@ -82,12 +82,14 @@ func utcDate(t time.Time) time.Time {
// computeUsageDateRange returns the [start, until] date range for incremental
collection.
// The Anthropic Analytics API only serves data from 2026-01-01 onwards and
requires
// dates to be at least three days old, so the current day and previous two
days are skipped.
+// since is the start time of the last successful run, so collection resumes
from the last
+// day that run collected.
func computeUsageDateRange(now time.Time, since *time.Time) (start, until
time.Time) {
apiFloor := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
until = utcDate(now).AddDate(0, 0, -claudeCodeAvailabilityLagDays)
start = until.AddDate(0, 0, -(claudeCodeInitialBackfillDays - 1))
if since != nil {
- start = utcDate(*since).AddDate(0, 0, 1)
+ start = utcDate(*since).AddDate(0, 0,
-claudeCodeAvailabilityLagDays)
minStart := until.AddDate(0, 0, -(claudeCodeInitialBackfillDays
- 1))
if start.Before(minStart) {
start = minStart
@@ -96,6 +98,9 @@ func computeUsageDateRange(now time.Time, since *time.Time)
(start, until time.T
if start.Before(apiFloor) {
start = apiFloor
}
+ if start.After(until) {
+ start = until
+ }
return start, until
}
diff --git a/backend/plugins/claude_code/tasks/collector_utils_test.go
b/backend/plugins/claude_code/tasks/collector_utils_test.go
index 86ba31656..b66ea059c 100644
--- a/backend/plugins/claude_code/tasks/collector_utils_test.go
+++ b/backend/plugins/claude_code/tasks/collector_utils_test.go
@@ -42,14 +42,57 @@ func TestComputeUsageDateRangeHonorsApiFloor(t *testing.T) {
assert.Equal(t, time.Date(2026, 3, 16, 0, 0, 0, 0, time.UTC), until)
}
-func TestComputeUsageDateRangeProducesEmptyWindowInsideLag(t *testing.T) {
+func TestComputeUsageDateRangeResumesFromLastCollectedDay(t *testing.T) {
now := time.Date(2026, 6, 19, 14, 30, 0, 0, time.UTC)
since := time.Date(2026, 6, 16, 12, 0, 0, 0, time.UTC)
start, until := computeUsageDateRange(now, &since)
- assert.Equal(t, time.Date(2026, 6, 17, 0, 0, 0, 0, time.UTC), start)
+ assert.Equal(t, time.Date(2026, 6, 13, 0, 0, 0, 0, time.UTC), start)
assert.Equal(t, time.Date(2026, 6, 16, 0, 0, 0, 0, time.UTC), until)
- assert.False(t, newClaudeCodeDayIterator(start, until).HasNext())
- assert.False(t, newClaudeCodeDateRangeIterator(start, until,
claudeCodeSummaryMaxDays).HasNext())
+}
+
+func TestComputeUsageDateRangeCollectsWhenSinceIsNow(t *testing.T) {
+ now := time.Date(2026, 9, 10, 12, 31, 0, 0, time.UTC)
+
+ start, until := computeUsageDateRange(now, &now)
+
+ assert.Equal(t, time.Date(2026, 9, 7, 0, 0, 0, 0, time.UTC), start)
+ assert.Equal(t, time.Date(2026, 9, 7, 0, 0, 0, 0, time.UTC), until)
+ assert.True(t, newClaudeCodeDayIterator(start, until).HasNext())
+ assert.True(t, newClaudeCodeDateRangeIterator(start, until,
claudeCodeSummaryMaxDays).HasNext())
+}
+
+func TestComputeUsageDateRangeCapsBackfillHorizon(t *testing.T) {
+ now := time.Date(2026, 9, 10, 12, 31, 0, 0, time.UTC)
+ since := time.Date(2026, 2, 1, 0, 0, 0, 0, time.UTC)
+
+ start, until := computeUsageDateRange(now, &since)
+
+ assert.Equal(t, time.Date(2026, 6, 10, 0, 0, 0, 0, time.UTC), start)
+ assert.Equal(t, time.Date(2026, 9, 7, 0, 0, 0, 0, time.UTC), until)
+}
+
+func TestComputeUsageDateRangeCollectsEveryDayAcrossRuns(t *testing.T) {
+ first := time.Date(2026, 8, 6, 7, 0, 0, 0, time.UTC)
+ for _, interval := range []int{1, 2, 3, 7} {
+ collected := map[time.Time]bool{}
+ var since *time.Time
+ var now time.Time
+ for run := 0; run < 6; run++ {
+ now = first.AddDate(0, 0, run*interval)
+ start, until := computeUsageDateRange(now, since)
+ for d := start; !d.After(until); d = d.AddDate(0, 0, 1)
{
+ collected[d] = true
+ }
+ runStart := now
+ since = &runStart
+ }
+
+ start, _ := computeUsageDateRange(first, nil)
+ _, until := computeUsageDateRange(now, nil)
+ for d := start; !d.After(until); d = d.AddDate(0, 0, 1) {
+ assert.Truef(t, collected[d], "interval %d: %s not
collected", interval, d.Format(claudeCodeDateLayout))
+ }
+ }
}