cloneDeviceRecord shares empty (non-nil) Metadata map between original and clone #683

Closed
opened 2026-03-28 04:27:24 +00:00 by mfreeman451 · 1 comment
Owner

Imported from GitHub.

Original GitHub issue: #2145
Original author: @mfreeman451
Original URL: https://github.com/carverauto/serviceradar/issues/2145
Original created: 2025-12-16T05:16:25Z


Summary

  • Context: cloneDeviceRecord in pkg/registry/device_store.go is used throughout the device registry to create defensive copies of device records, ensuring that callers can safely modify returned records without affecting the original stored data.
  • Bug: When cloning a DeviceRecord with an empty (but not nil) Metadata map, the function fails to deep-copy the map, causing both the original and clone to share the same underlying map reference.
  • Actual vs. expected: Modifications to the cloned record's Metadata map directly affect the original stored record's Metadata, instead of the two records having independent metadata maps.
  • Impact: Data corruption in the device registry's in-memory store, where concurrent or sequential operations on "independent" device record copies can unexpectedly overwrite each other's metadata changes, leading to incorrect device state and potential race conditions.

Code with bug

func cloneDeviceRecord(src *DeviceRecord) *DeviceRecord {
	if src == nil {
		return nil
	}

	dst := *src

	if src.Hostname != nil {
		hostname := *src.Hostname
		dst.Hostname = &hostname
	}
	if src.MAC != nil {
		mac := *src.MAC
		dst.MAC = &mac
	}
	if src.IntegrationID != nil {
		integrationID := *src.IntegrationID
		dst.IntegrationID = &integrationID
	}
	if src.CollectorAgentID != nil {
		collectorAgentID := *src.CollectorAgentID
		dst.CollectorAgentID = &collectorAgentID
	}

	if len(src.DiscoverySources) > 0 {
		dst.DiscoverySources = append([]string(nil), src.DiscoverySources...)
	}
	if len(src.Capabilities) > 0 {
		dst.Capabilities = append([]string(nil), src.Capabilities...)
	}
	if len(src.Metadata) > 0 {  // <-- BUG 🔴 This condition is false for empty maps
		meta := make(map[string]string, len(src.Metadata))
		for k, v := range src.Metadata {
			meta[k] = v
		}
		dst.Metadata = meta
	}

	return &dst
}

The bug is at line 204 in pkg/registry/device_store.go. When src.Metadata is an empty map (length 0 but not nil), the condition if len(src.Metadata) > 0 evaluates to false, so the map is not cloned. Since dst := *src creates a shallow copy of the struct, dst.Metadata still points to the same underlying map as src.Metadata.

Evidence

Example

Consider the following sequence:

  1. A DeviceRecord is stored in the registry with Metadata: map[string]string{}
  2. Caller A retrieves the record via GetDeviceRecord(), which returns a clone
  3. Caller A modifies the clone: clone.Metadata["key1"] = "value1"
  4. Caller B retrieves the same record via GetDeviceRecord(), which returns another clone
  5. BUG: Caller B's clone already contains key1 = "value1" even though they expect a fresh copy

This happens because:

  • Step 1: The registry stores record R with empty map M at address 0x1000
  • Step 2: cloneDeviceRecord(R) returns clone C1, but C1.Metadata still points to 0x1000 (not copied)
  • Step 3: Caller A writes to 0x1000["key1"]
  • Step 4: cloneDeviceRecord(R) returns clone C2, but C2.Metadata still points to 0x1000
  • Step 5: Both R.Metadata, C1.Metadata, and C2.Metadata point to the same map at 0x1000

Failing test

Test script

package registry

import (
	"testing"
)

// TestCloneDeviceRecord_EmptyMapIsolation verifies that cloning a DeviceRecord
// with an empty (but not nil) Metadata map creates an independent copy.
func TestCloneDeviceRecord_EmptyMapIsolation(t *testing.T) {
	// Create a record with an empty (but not nil) metadata map
	original := &DeviceRecord{
		DeviceID: "device-1",
		IP:       "10.0.0.1",
		Metadata: map[string]string{}, // Empty map, not nil
	}

	// Clone the record
	clone := cloneDeviceRecord(original)

	// Modify the clone's metadata
	clone.Metadata["test_key"] = "test_value"

	// The original should NOT be affected
	if len(original.Metadata) > 0 {
		t.Errorf("BUG: Modifying clone's metadata affected the original! Original.Metadata = %v", original.Metadata)
	}

	// The clone should have the new value
	if clone.Metadata["test_key"] != "test_value" {
		t.Errorf("Expected clone to have test_key=test_value, got: %v", clone.Metadata)
	}
}

// TestGetDeviceRecord_ModifyingCloneAffectsOriginal_EmptyMap demonstrates
// a realistic scenario where the cloning bug causes data corruption.
func TestGetDeviceRecord_ModifyingCloneAffectsOriginal_EmptyMap(t *testing.T) {
	reg := newTestDeviceRegistry()

	// Insert a device with an empty (but not nil) metadata map
	originalRecord := &DeviceRecord{
		DeviceID: "device-1",
		IP:       "10.0.0.1",
		Metadata: map[string]string{}, // Empty but not nil
	}
	reg.UpsertDeviceRecord(originalRecord)

	// Retrieve the device (which should return a clone)
	clone1, ok := reg.GetDeviceRecord("device-1")
	if !ok {
		t.Fatal("Expected to find device-1")
	}

	// Verify the clone starts empty
	if len(clone1.Metadata) != 0 {
		t.Fatalf("Expected clone to have empty metadata, got: %v", clone1.Metadata)
	}

	// Modify the clone's metadata
	clone1.Metadata["key1"] = "value1"

	// Retrieve the device again
	clone2, ok := reg.GetDeviceRecord("device-1")
	if !ok {
		t.Fatal("Expected to find device-1")
	}

	// BUG: The second clone should NOT have the modifications from clone1
	if val, exists := clone2.Metadata["key1"]; exists {
		t.Errorf("BUG: Modifications to clone1 leaked into clone2! clone2.Metadata = %v", clone2.Metadata)
		t.Errorf("Expected clone2.Metadata to be empty, but found key1=%v", val)
	}
}

// TestGetDeviceRecord_RaceCondition shows how this bug could cause a race condition
func TestGetDeviceRecord_RaceCondition(t *testing.T) {
	reg := newTestDeviceRegistry()

	// Insert a device with empty metadata
	reg.UpsertDeviceRecord(&DeviceRecord{
		DeviceID: "device-1",
		IP:       "10.0.0.1",
		Metadata: map[string]string{},
	})

	// Two different callers retrieve the device
	caller1, ok1 := reg.GetDeviceRecord("device-1")
	caller2, ok2 := reg.GetDeviceRecord("device-1")

	if !ok1 || !ok2 {
		t.Fatal("Expected to find device-1")
	}

	// Both callers modify their "independent" copies
	caller1.Metadata["source"] = "caller1"
	caller2.Metadata["source"] = "caller2"

	// BUG: One caller's modification overwrites the other's
	if caller1.Metadata["source"] == "caller2" {
		t.Errorf("BUG: caller2's modification affected caller1's copy!")
	}
}

Test output

Running go test -v -count=1 ./pkg/registry -run TestCloneDeviceRecord_EmptyMapIsolation:

=== RUN   TestCloneDeviceRecord_EmptyMapIsolation
    device_store_clone_test.go:25: BUG: Modifying clone's metadata affected the original! Original.Metadata = map[test_key:test_value]
--- FAIL: TestCloneDeviceRecord_EmptyMapIsolation (0.00s)
FAIL
FAIL	github.com/carverauto/serviceradar/pkg/registry	0.005s
FAIL

Running go test -v -count=1 ./pkg/registry -run TestGetDeviceRecord_ModifyingCloneAffectsOriginal_EmptyMap:

=== RUN   TestGetDeviceRecord_ModifyingCloneAffectsOriginal_EmptyMap
    device_store_realistic_bug_test.go:44: BUG: Modifications to clone1 leaked into clone2! clone2.Metadata = map[key1:value1]
    device_store_realistic_bug_test.go:45: Expected clone2.Metadata to be empty, but found key1=value1
--- FAIL: TestGetDeviceRecord_ModifyingCloneAffectsOriginal_EmptyMap (0.00s)
FAIL
FAIL	github.com/carverauto/serviceradar/pkg/registry	0.006s
FAIL

Running go test -v -count=1 ./pkg/registry -run TestGetDeviceRecord_RaceCondition:

=== RUN   TestGetDeviceRecord_RaceCondition
    device_store_realistic_bug_test.go:81: BUG: caller2's modification affected caller1's copy!
--- FAIL: TestGetDeviceRecord_RaceCondition (0.00s)
FAIL
FAIL	github.com/carverauto/serviceradar/pkg/registry	0.006s
FAIL

Full context

The device registry maintains an in-memory cache of device records (DeviceRegistry.devices map[string]*DeviceRecord). To provide defensive copying and prevent external callers from corrupting this internal state, all getter methods (GetDeviceRecord, FindDevicesByIP, FindDevicesByMAC, SnapshotRecords) use cloneDeviceRecord to return deep copies of records.

Empty (but not nil) Metadata maps can be created in several ways:

  1. Direct initialization with empty maps: Throughout the codebase, there are many instances where code initializes metadata as map[string]string{} (e.g., in tests at pkg/registry/registry_test.go:284, pkg/registry/service_device_test.go:297)

  2. Via mergeMetadata function: In pkg/registry/device_store_update.go:145-163, the mergeMetadata function can produce empty maps when:

    • dest is nil
    • src has entries but they all have empty/whitespace-only keys
    • Example: mergeMetadata(nil, map[string]string{"": "value"}) returns an empty (non-nil) map
  3. Via various update flows: The registry processes device updates through multiple integration sources (Armis, Netbox, sweep discovery), and metadata can start as an initialized empty map (e.g., pkg/registry/registry.go:1622, pkg/db/db.go:354)

The bug affects any code path that:

  1. Retrieves a device record using GetDeviceRecord()
  2. Modifies the returned record's Metadata map
  3. Expects those modifications to be isolated from other callers

Key affected call sites:

  • pkg/registry/device_store_update.go:34: Gets existing record, passes to deviceRecordFromUpdate which may mutate it
  • pkg/registry/registry.go:1896: Public API GetDevice() returns cloned records
  • pkg/registry/registry.go:1960: Search functionality retrieves and operates on cloned records
  • pkg/registry/registry.go:2044: FindRelatedDevices() retrieves cloned records

Why has this bug gone undetected?

This bug has remained undetected for several reasons:

  1. Uncommon edge case: Empty (but not nil) metadata maps are less common than either nil maps or maps with content. Most device records in production have at least some metadata entries, which would trigger the cloning logic and work correctly.

  2. Defensive pattern elsewhere: The codebase has defensive checks like if update.Metadata == nil { update.Metadata = make(map[string]string) } (e.g., pkg/registry/registry.go:1621-1623) before writing to metadata. This pattern masks the bug because it creates a new map before writing, preventing corruption of the shared empty map.

  3. Existing tests don't validate isolation: The existing test TestUpsertAndGetDeviceRecord at pkg/registry/device_store_test.go:60-78 does test mutation isolation, but it uses a record with non-empty metadata (metadata := map[string]string{"region": testRegionUSEast1}), so the cloning logic works correctly in that test.

  4. Timing-dependent manifestation: The bug only manifests when:

    • A record starts with empty (not nil) metadata
    • The record is retrieved multiple times
    • The cloned copies are modified
    • This specific sequence is less common in actual usage patterns
  5. Map reference equality not tested: None of the existing tests verify that cloned records have independent map instances (by checking if modifications to one clone affect another), they only check value equality at a single point in time.

Recommended fix

Change the condition at line 204 in pkg/registry/device_store.go from if len(src.Metadata) > 0 to if src.Metadata != nil:

if src.Metadata != nil {  // <-- FIX 🟢 Check for nil instead of length
	meta := make(map[string]string, len(src.Metadata))
	for k, v := range src.Metadata {
		meta[k] = v
	}
	dst.Metadata = meta
}

This ensures that any non-nil map (including empty maps) gets deep-copied, while truly nil maps remain nil in the clone.

The same issue likely affects the slice fields (DiscoverySources and Capabilities), though in practice this is less problematic because Go slice headers are copied by value, and the append operations used in the code typically create new underlying arrays. However, for consistency and safety, these should also be fixed:

if src.DiscoverySources != nil {  // <-- FIX 🟢 Check for nil instead of length
	dst.DiscoverySources = append([]string(nil), src.DiscoverySources...)
}
if src.Capabilities != nil {  // <-- FIX 🟢 Check for nil instead of length
	dst.Capabilities = append([]string(nil), src.Capabilities...)
}
Imported from GitHub. Original GitHub issue: #2145 Original author: @mfreeman451 Original URL: https://github.com/carverauto/serviceradar/issues/2145 Original created: 2025-12-16T05:16:25Z --- # Summary - **Context**: `cloneDeviceRecord` in `pkg/registry/device_store.go` is used throughout the device registry to create defensive copies of device records, ensuring that callers can safely modify returned records without affecting the original stored data. - **Bug**: When cloning a `DeviceRecord` with an empty (but not nil) `Metadata` map, the function fails to deep-copy the map, causing both the original and clone to share the same underlying map reference. - **Actual vs. expected**: Modifications to the cloned record's `Metadata` map directly affect the original stored record's `Metadata`, instead of the two records having independent metadata maps. - **Impact**: Data corruption in the device registry's in-memory store, where concurrent or sequential operations on "independent" device record copies can unexpectedly overwrite each other's metadata changes, leading to incorrect device state and potential race conditions. # Code with bug ```go func cloneDeviceRecord(src *DeviceRecord) *DeviceRecord { if src == nil { return nil } dst := *src if src.Hostname != nil { hostname := *src.Hostname dst.Hostname = &hostname } if src.MAC != nil { mac := *src.MAC dst.MAC = &mac } if src.IntegrationID != nil { integrationID := *src.IntegrationID dst.IntegrationID = &integrationID } if src.CollectorAgentID != nil { collectorAgentID := *src.CollectorAgentID dst.CollectorAgentID = &collectorAgentID } if len(src.DiscoverySources) > 0 { dst.DiscoverySources = append([]string(nil), src.DiscoverySources...) } if len(src.Capabilities) > 0 { dst.Capabilities = append([]string(nil), src.Capabilities...) } if len(src.Metadata) > 0 { // <-- BUG 🔴 This condition is false for empty maps meta := make(map[string]string, len(src.Metadata)) for k, v := range src.Metadata { meta[k] = v } dst.Metadata = meta } return &dst } ``` The bug is at line 204 in `pkg/registry/device_store.go`. When `src.Metadata` is an empty map (length 0 but not nil), the condition `if len(src.Metadata) > 0` evaluates to false, so the map is not cloned. Since `dst := *src` creates a shallow copy of the struct, `dst.Metadata` still points to the same underlying map as `src.Metadata`. # Evidence ## Example Consider the following sequence: 1. A `DeviceRecord` is stored in the registry with `Metadata: map[string]string{}` 2. Caller A retrieves the record via `GetDeviceRecord()`, which returns a clone 3. Caller A modifies the clone: `clone.Metadata["key1"] = "value1"` 4. Caller B retrieves the same record via `GetDeviceRecord()`, which returns another clone 5. **BUG**: Caller B's clone already contains `key1 = "value1"` even though they expect a fresh copy This happens because: - Step 1: The registry stores record R with empty map M at address 0x1000 - Step 2: `cloneDeviceRecord(R)` returns clone C1, but C1.Metadata still points to 0x1000 (not copied) - Step 3: Caller A writes to 0x1000["key1"] - Step 4: `cloneDeviceRecord(R)` returns clone C2, but C2.Metadata still points to 0x1000 - Step 5: Both R.Metadata, C1.Metadata, and C2.Metadata point to the same map at 0x1000 ## Failing test ### Test script ```go package registry import ( "testing" ) // TestCloneDeviceRecord_EmptyMapIsolation verifies that cloning a DeviceRecord // with an empty (but not nil) Metadata map creates an independent copy. func TestCloneDeviceRecord_EmptyMapIsolation(t *testing.T) { // Create a record with an empty (but not nil) metadata map original := &DeviceRecord{ DeviceID: "device-1", IP: "10.0.0.1", Metadata: map[string]string{}, // Empty map, not nil } // Clone the record clone := cloneDeviceRecord(original) // Modify the clone's metadata clone.Metadata["test_key"] = "test_value" // The original should NOT be affected if len(original.Metadata) > 0 { t.Errorf("BUG: Modifying clone's metadata affected the original! Original.Metadata = %v", original.Metadata) } // The clone should have the new value if clone.Metadata["test_key"] != "test_value" { t.Errorf("Expected clone to have test_key=test_value, got: %v", clone.Metadata) } } // TestGetDeviceRecord_ModifyingCloneAffectsOriginal_EmptyMap demonstrates // a realistic scenario where the cloning bug causes data corruption. func TestGetDeviceRecord_ModifyingCloneAffectsOriginal_EmptyMap(t *testing.T) { reg := newTestDeviceRegistry() // Insert a device with an empty (but not nil) metadata map originalRecord := &DeviceRecord{ DeviceID: "device-1", IP: "10.0.0.1", Metadata: map[string]string{}, // Empty but not nil } reg.UpsertDeviceRecord(originalRecord) // Retrieve the device (which should return a clone) clone1, ok := reg.GetDeviceRecord("device-1") if !ok { t.Fatal("Expected to find device-1") } // Verify the clone starts empty if len(clone1.Metadata) != 0 { t.Fatalf("Expected clone to have empty metadata, got: %v", clone1.Metadata) } // Modify the clone's metadata clone1.Metadata["key1"] = "value1" // Retrieve the device again clone2, ok := reg.GetDeviceRecord("device-1") if !ok { t.Fatal("Expected to find device-1") } // BUG: The second clone should NOT have the modifications from clone1 if val, exists := clone2.Metadata["key1"]; exists { t.Errorf("BUG: Modifications to clone1 leaked into clone2! clone2.Metadata = %v", clone2.Metadata) t.Errorf("Expected clone2.Metadata to be empty, but found key1=%v", val) } } // TestGetDeviceRecord_RaceCondition shows how this bug could cause a race condition func TestGetDeviceRecord_RaceCondition(t *testing.T) { reg := newTestDeviceRegistry() // Insert a device with empty metadata reg.UpsertDeviceRecord(&DeviceRecord{ DeviceID: "device-1", IP: "10.0.0.1", Metadata: map[string]string{}, }) // Two different callers retrieve the device caller1, ok1 := reg.GetDeviceRecord("device-1") caller2, ok2 := reg.GetDeviceRecord("device-1") if !ok1 || !ok2 { t.Fatal("Expected to find device-1") } // Both callers modify their "independent" copies caller1.Metadata["source"] = "caller1" caller2.Metadata["source"] = "caller2" // BUG: One caller's modification overwrites the other's if caller1.Metadata["source"] == "caller2" { t.Errorf("BUG: caller2's modification affected caller1's copy!") } } ``` ### Test output Running `go test -v -count=1 ./pkg/registry -run TestCloneDeviceRecord_EmptyMapIsolation`: ``` === RUN TestCloneDeviceRecord_EmptyMapIsolation device_store_clone_test.go:25: BUG: Modifying clone's metadata affected the original! Original.Metadata = map[test_key:test_value] --- FAIL: TestCloneDeviceRecord_EmptyMapIsolation (0.00s) FAIL FAIL github.com/carverauto/serviceradar/pkg/registry 0.005s FAIL ``` Running `go test -v -count=1 ./pkg/registry -run TestGetDeviceRecord_ModifyingCloneAffectsOriginal_EmptyMap`: ``` === RUN TestGetDeviceRecord_ModifyingCloneAffectsOriginal_EmptyMap device_store_realistic_bug_test.go:44: BUG: Modifications to clone1 leaked into clone2! clone2.Metadata = map[key1:value1] device_store_realistic_bug_test.go:45: Expected clone2.Metadata to be empty, but found key1=value1 --- FAIL: TestGetDeviceRecord_ModifyingCloneAffectsOriginal_EmptyMap (0.00s) FAIL FAIL github.com/carverauto/serviceradar/pkg/registry 0.006s FAIL ``` Running `go test -v -count=1 ./pkg/registry -run TestGetDeviceRecord_RaceCondition`: ``` === RUN TestGetDeviceRecord_RaceCondition device_store_realistic_bug_test.go:81: BUG: caller2's modification affected caller1's copy! --- FAIL: TestGetDeviceRecord_RaceCondition (0.00s) FAIL FAIL github.com/carverauto/serviceradar/pkg/registry 0.006s FAIL ``` # Full context The device registry maintains an in-memory cache of device records (`DeviceRegistry.devices map[string]*DeviceRecord`). To provide defensive copying and prevent external callers from corrupting this internal state, all getter methods (`GetDeviceRecord`, `FindDevicesByIP`, `FindDevicesByMAC`, `SnapshotRecords`) use `cloneDeviceRecord` to return deep copies of records. Empty (but not nil) `Metadata` maps can be created in several ways: 1. **Direct initialization with empty maps**: Throughout the codebase, there are many instances where code initializes metadata as `map[string]string{}` (e.g., in tests at `pkg/registry/registry_test.go:284`, `pkg/registry/service_device_test.go:297`) 2. **Via `mergeMetadata` function**: In `pkg/registry/device_store_update.go:145-163`, the `mergeMetadata` function can produce empty maps when: - `dest` is nil - `src` has entries but they all have empty/whitespace-only keys - Example: `mergeMetadata(nil, map[string]string{"": "value"})` returns an empty (non-nil) map 3. **Via various update flows**: The registry processes device updates through multiple integration sources (Armis, Netbox, sweep discovery), and metadata can start as an initialized empty map (e.g., `pkg/registry/registry.go:1622`, `pkg/db/db.go:354`) The bug affects any code path that: 1. Retrieves a device record using `GetDeviceRecord()` 2. Modifies the returned record's `Metadata` map 3. Expects those modifications to be isolated from other callers Key affected call sites: - `pkg/registry/device_store_update.go:34`: Gets existing record, passes to `deviceRecordFromUpdate` which may mutate it - `pkg/registry/registry.go:1896`: Public API `GetDevice()` returns cloned records - `pkg/registry/registry.go:1960`: Search functionality retrieves and operates on cloned records - `pkg/registry/registry.go:2044`: `FindRelatedDevices()` retrieves cloned records # Why has this bug gone undetected? This bug has remained undetected for several reasons: 1. **Uncommon edge case**: Empty (but not nil) metadata maps are less common than either nil maps or maps with content. Most device records in production have at least some metadata entries, which would trigger the cloning logic and work correctly. 2. **Defensive pattern elsewhere**: The codebase has defensive checks like `if update.Metadata == nil { update.Metadata = make(map[string]string) }` (e.g., `pkg/registry/registry.go:1621-1623`) before writing to metadata. This pattern masks the bug because it creates a new map before writing, preventing corruption of the shared empty map. 3. **Existing tests don't validate isolation**: The existing test `TestUpsertAndGetDeviceRecord` at `pkg/registry/device_store_test.go:60-78` does test mutation isolation, but it uses a record with non-empty metadata (`metadata := map[string]string{"region": testRegionUSEast1}`), so the cloning logic works correctly in that test. 4. **Timing-dependent manifestation**: The bug only manifests when: - A record starts with empty (not nil) metadata - The record is retrieved multiple times - The cloned copies are modified - This specific sequence is less common in actual usage patterns 5. **Map reference equality not tested**: None of the existing tests verify that cloned records have independent map instances (by checking if modifications to one clone affect another), they only check value equality at a single point in time. # Recommended fix Change the condition at line 204 in `pkg/registry/device_store.go` from `if len(src.Metadata) > 0` to `if src.Metadata != nil`: ```go if src.Metadata != nil { // <-- FIX 🟢 Check for nil instead of length meta := make(map[string]string, len(src.Metadata)) for k, v := range src.Metadata { meta[k] = v } dst.Metadata = meta } ``` This ensures that any non-nil map (including empty maps) gets deep-copied, while truly nil maps remain nil in the clone. The same issue likely affects the slice fields (`DiscoverySources` and `Capabilities`), though in practice this is less problematic because Go slice headers are copied by value, and the append operations used in the code typically create new underlying arrays. However, for consistency and safety, these should also be fixed: ```go if src.DiscoverySources != nil { // <-- FIX 🟢 Check for nil instead of length dst.DiscoverySources = append([]string(nil), src.DiscoverySources...) } if src.Capabilities != nil { // <-- FIX 🟢 Check for nil instead of length dst.Capabilities = append([]string(nil), src.Capabilities...) } ```
Author
Owner

Imported GitHub comment.

Original author: @mfreeman451
Original URL: https://github.com/carverauto/serviceradar/issues/2145#issuecomment-3663012986
Original created: 2025-12-17T00:20:13Z


closing as completed

Imported GitHub comment. Original author: @mfreeman451 Original URL: https://github.com/carverauto/serviceradar/issues/2145#issuecomment-3663012986 Original created: 2025-12-17T00:20:13Z --- closing as completed
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
carverauto/serviceradar#683
No description provided.