Skip to content

Conversation

@andybraren
Copy link
Collaborator

Still need to test this with an updated backend, but this should add per-message timestamps to the eventstream from AGUI, enabling us to show accurate per-message timestamps. Right now all message timestamps in a session change to the time of the most recently received message.

- Added logic to set a timestamp for AGUI events in `agui_proxy.go` and `agui.go` if not already present, ensuring accurate message tracking.
- Updated `compaction.go` to preserve the timestamp from events when creating messages.
- Modified frontend components to utilize the timestamp from backend messages, providing a fallback for legacy messages.

This change improves the reliability of message timestamp tracking across the system.
@github-actions
Copy link
Contributor

github-actions bot commented Jan 23, 2026

Claude Code Review

Summary

This PR fixes a critical timestamp tracking issue in the AG-UI event streaming system. Previously, all messages in a session would display the timestamp of the most recently received message. The fix ensures that each message captures its own timestamp at the point it's created/received, both on the backend (Go) and frontend (TypeScript).

Overall Assessment:Approved with Minor Recommendations

The implementation correctly addresses the stated problem following established patterns. The changes are minimal, focused, and backwards-compatible.


Issues by Severity

🚫 Blocker Issues

None - This PR is ready to merge.

🔴 Critical Issues

None identified.

🟡 Major Issues

1. Inconsistent Timestamp Format Handling (components/backend/websocket/compaction.go:122)

  • Issue: The code uses type assertion timestamp, _ := event["timestamp"].(string) without validation
  • Risk: If timestamp is not a string (e.g., already formatted time.Time), it silently fails with empty string
  • Pattern Violation: Violates type-safe unstructured access pattern from CLAUDE.md
  • Recommendation:
    // CURRENT (line 122, 234)
    timestamp, _ := event["timestamp"].(string)
    
    // BETTER - validate type and fallback
    timestamp := ""
    if ts, ok := event["timestamp"].(string); ok && ts \!= "" {
        timestamp = ts
    }
  • Impact: May result in missing timestamps if event data format changes

2. Duplicate Timestamp Assignment Logic (components/backend/websocket/agui.go:126-129, 179-181)

  • Issue: Same timestamp assignment logic duplicated in two locations
  • DRY Principle: Should extract to helper function
  • Recommendation:
    // Add helper function
    func ensureTimestamp(event map[string]interface{}) {
        if event["timestamp"] == nil || event["timestamp"] == "" {
            event["timestamp"] = time.Now().UTC().Format(time.RFC3339Nano)
        }
    }
    
    // Use in both locations:
    ensureTimestamp(event)
  • Impact: Maintenance burden, potential for divergence

🔵 Minor Issues

1. Comment Clarity (components/frontend/src/app/projects/[name]/sessions/[sessionName]/page.tsx:762-763)

  • Issue: Comment says "After backend fix, new messages will have proper timestamps" but doesn't mention what happens during the transition period
  • Recommendation:
    // Use msg.timestamp from backend (RFC3339Nano format)
    // Fallback to current time for legacy messages from before PR #521
    const timestamp = msg.timestamp || new Date().toISOString();

2. Missing Test Coverage

  • Issue: No tests verify timestamp preservation through the event pipeline
  • Recommendation: Add integration test that:
    1. Sends event without timestamp
    2. Verifies backend adds timestamp
    3. Confirms frontend receives and displays correct timestamp
    4. Validates timestamp doesn't change on subsequent events
  • File: Consider adding to components/backend/websocket/agui_test.go

3. Frontend Type Safety (components/frontend/src/hooks/use-agui-stream.ts)

  • Observation: Frontend code assumes event.timestamp exists and is a string
  • Current: Works because backend now guarantees timestamp presence
  • Recommendation: Document this contract dependency or add runtime validation:
    timestamp: event.timestamp || new Date().toISOString(),  // Defensive fallback

Positive Highlights

Excellent Pattern Adherence:

  • Uses time.RFC3339Nano consistently across all timestamp assignments (Go standard)
  • Properly handles nil and empty string checks before assignment
  • Frontend correctly uses optional chaining (`msg.timestamp``) with fallback

Strategic Placement:

  • Timestamps set at event routing entry points (agui.go, agui_proxy.go)
  • Preserved through message compaction (compaction.go)
  • Captured in frontend message state (use-agui-stream.ts)

Backwards Compatibility:

  • Fallback to new Date().toISOString() for legacy messages without timestamps
  • No breaking changes to existing API contracts
  • Type definitions already included timestamp?: string (optional field)

Good Comments:

  • "Critical for message timestamp tracking" clearly explains importance
  • Comments in frontend explain the fallback behavior

Minimal Scope:

  • Focused on timestamp issue only
  • No unnecessary refactoring or feature additions
  • Clean, reviewable diff (38 additions, 5 deletions)

Recommendations

Priority 1: Before Merge

  1. Extract duplicate timestamp logic to helper function (DRY principle)
  2. Add type validation for timestamp extraction in compaction.go

Priority 2: Follow-up PR

  1. Add integration test for timestamp preservation
  2. Document timestamp contract in types/agui.go Message struct
  3. Consider adding validation in frontend to detect missing timestamps and log warning

Priority 3: Future Improvement

  1. Evaluate using Go time.Time instead of string in backend types, then format to RFC3339Nano only at JSON serialization boundary
  2. Add timestamp to BaseEvent type if not already present for consistency

Architecture Compliance

Backend Standards (CLAUDE.md):

  • Error handling follows pattern (no panic, proper logging)
  • Type handling mostly correct (minor improvement needed)
  • Code organization appropriate for websocket package

Frontend Standards:

  • TypeScript types properly used (no any types)
  • React hooks pattern followed correctly
  • Optional chaining used appropriately

⚠️ Minor Deviation:

  • Backend: Direct type assertion without validation (compaction.go) violates type-safety pattern
  • Should use validation pattern from CLAUDE.md section "Type-Safe Unstructured Access"

Testing Verification

Manual Testing Needed:

  • Create new session and verify each message shows correct individual timestamp
  • Verify timestamps don't all change to most recent message time
  • Check that older messages (created before this PR) still display (with fallback timestamp)
  • Confirm timestamp format is consistent (RFC3339Nano from backend, ISO8601 from frontend fallback)

Automated Testing Recommendations:

// Add to components/backend/websocket/agui_test.go
func TestEventTimestampPreservation(t *testing.T) {
    event := map[string]interface{}{
        "type": "text.message.start",
        "messageId": "msg-123",
    }
    
    // Should add timestamp if missing
    RouteAGUIEvent("test-session", event)
    
    if event["timestamp"] == nil {
        t.Error("Expected timestamp to be set")
    }
    
    originalTimestamp := event["timestamp"]
    time.Sleep(10 * time.Millisecond)
    
    // Should NOT overwrite existing timestamp
    event["timestamp"] = originalTimestamp
    RouteAGUIEvent("test-session", event)
    
    if event["timestamp"] \!= originalTimestamp {
        t.Error("Timestamp was unexpectedly modified")
    }
}

Security Review

✅ No security concerns identified

  • Timestamps are system-generated, not user input
  • No risk of injection attacks
  • No sensitive data exposure

Performance Impact

✅ Minimal performance impact

  • time.Now().UTC().Format() is efficient
  • Only executed when timestamp missing
  • No additional network calls or database queries

Final Verdict

Recommendation: ✅ APPROVE with minor improvements

This PR correctly solves the stated problem. The timestamp tracking implementation is sound and follows most established patterns. The suggested improvements are for code quality and maintainability, not correctness.

Before Merge:

  • Consider extracting duplicate timestamp logic to helper function
  • Add type validation for timestamp extraction in compaction.go

After Merge:

  • Add integration test for timestamp preservation
  • Document the timestamp contract in types

Great work on identifying and fixing this UX issue! The message timestamps will now correctly reflect when each message was created rather than all showing the most recent message time.


🔍 View AI decision process (logs available for 90 days)

📋 View memory system files loaded (click to expand)

What Amber Loaded for Code Review

Amber automatically loaded these repository standards from the memory system:

  1. CLAUDE.md - Master project instructions, development standards
  2. backend-development.md - Go backend, K8s integration patterns
  3. frontend-development.md - NextJS, Shadcn UI, React Query patterns
  4. security-standards.md - Auth, RBAC, token handling
  5. k8s-client-usage.md - User token vs service account patterns
  6. error-handling.md - Consistent error patterns
  7. react-query-usage.md - Data fetching patterns

Impact: This review used your repository's specific code quality standards, security patterns, and best practices from the memory system (PRs #359, #360) - not just generic code review guidelines.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant