diff --git a/internal/fleetobserve/observe.go b/internal/fleetobserve/observe.go index 9989fb0..e485a62 100644 --- a/internal/fleetobserve/observe.go +++ b/internal/fleetobserve/observe.go @@ -596,7 +596,7 @@ func summarizeQueue(snapshot queueintent.Snapshot, platform config.Config, now t correlationAge := now.Sub(intent.QueueTime) if !strings.Contains(intent.Repository, "/") { summary.UnboundRepository++ - if correlationAge >= queueCorrelationGracePeriod { + if intent.State != queueintent.StateQueued && correlationAge >= queueCorrelationGracePeriod { summary.UnboundRepositoryBeyondGrace++ } } @@ -609,7 +609,11 @@ func summarizeQueue(snapshot queueintent.Snapshot, platform config.Config, now t } if intent.WorkflowRunID == 0 { summary.MissingWorkflowRunID++ - if correlationAge >= queueCorrelationGracePeriod { + // A rehydrated GitHub job can wait in queued state for capacity + // without a workflow-run identity; the authenticated runner claim + // supplies that identity after assignment. Keep the raw gap visible, + // but page only after the intent has actually left the queue. + if intent.State != queueintent.StateQueued && correlationAge >= queueCorrelationGracePeriod { summary.MissingWorkflowRunIDBeyondGrace++ } } diff --git a/internal/fleetobserve/observe_test.go b/internal/fleetobserve/observe_test.go index 3526779..503e0b9 100644 --- a/internal/fleetobserve/observe_test.go +++ b/internal/fleetobserve/observe_test.go @@ -284,6 +284,26 @@ func TestQueueSummarySeparatesTransientAndPersistentCorrelationGaps(t *testing.T } } +func TestQueueSummaryDoesNotPageOnRehydratedJobWaitingForCapacity(t *testing.T) { + queued := queueintent.Intent{ + Key: "rehydrated", ScaleSetID: 11, JobID: "5c3077ba-3664-5824-b2cf-e22a31b25f43", + ScaleSetName: "nddev-linux-integration", Repository: "owner", + WorkflowRef: "authoritative-rehydration", EventName: "push", + QueueTime: observationTime.Add(-10 * queueCorrelationGracePeriod), + State: queueintent.StateQueued, Priority: 1, + StateEnteredAt: observationTime.Add(-time.Minute), UpdatedAt: observationTime.Add(-time.Second), + ExpiresAt: observationTime.Add(time.Minute), + } + summary, err := summarizeQueue(queueintent.Snapshot{Active: []queueintent.Intent{queued}}, testPlatform(t), observationTime) + if err != nil { + t.Fatal(err) + } + if summary.MissingWorkflowRunID != 1 || summary.MissingWorkflowRunIDBeyondGrace != 0 || + summary.UnboundRepository != 1 || summary.UnboundRepositoryBeyondGrace != 0 { + t.Fatalf("queued correlation classification = %#v", summary) + } +} + func TestCollectorMarksRunningIntentWithoutExecutionLeaseUnhealthy(t *testing.T) { collector := healthyCollector(t) collector.Journal = func(context.Context) (providerjournal.Journal, error) {