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))
+               }
+       }
 }

Reply via email to