Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion backend/internal/lifecycle/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,11 @@ type Manager struct {

// New builds a Lifecycle Manager over the session store it writes and the messenger it uses for agent nudges.
func New(store sessionStore, messenger ports.AgentMessenger) *Manager {
return &Manager{store: store, messenger: messenger, window: defaultRecentActivityWindow, clock: time.Now, react: newReactionState()}
// UTC so activity-driven LastActivityAt/UpdatedAt match spawn-stamped
// timestamps (the session manager clock is UTC too); a local clock here left
// `ao session get` showing created in UTC but updated in local time.
clock := func() time.Time { return time.Now().UTC() }
return &Manager{store: store, messenger: messenger, window: defaultRecentActivityWindow, clock: clock, react: newReactionState()}
}

func (m *Manager) mutate(ctx context.Context, id domain.SessionID, fn func(domain.SessionRecord, time.Time) (domain.SessionRecord, bool)) error {
Expand Down
15 changes: 15 additions & 0 deletions backend/internal/lifecycle/manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,21 @@ func TestMarkSpawnedStoresRuntimeMetadata(t *testing.T) {
}
}

// TestMarkSpawned_StampsUTCActivity locks the lifecycle clock to UTC so
// activity-driven timestamps match the session manager's spawn timestamps. A
// local clock here left `ao session get` showing created in UTC but updated in
// local time.
func TestMarkSpawned_StampsUTCActivity(t *testing.T) {
m, st, _ := newManager()
st.sessions["mer-1"] = domain.SessionRecord{ID: "mer-1", ProjectID: "mer", IsTerminated: true}
if err := m.MarkSpawned(ctx, "mer-1", domain.SessionMetadata{RuntimeHandleID: "h1"}); err != nil {
t.Fatal(err)
}
if loc := st.sessions["mer-1"].Activity.LastActivityAt.Location(); loc != time.UTC {
t.Fatalf("LastActivityAt location = %v, want UTC", loc)
}
}

func TestPRObservation_CIFailingNudgesAgentWithLogs(t *testing.T) {
m, st, msg := newManager()
st.sessions["mer-1"] = working("mer-1")
Expand Down
5 changes: 4 additions & 1 deletion backend/internal/session_manager/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,10 @@ func New(d Deps) *Manager {
logger: d.Logger,
}
if m.clock == nil {
m.clock = time.Now
// UTC so spawn-stamped CreatedAt/UpdatedAt match every other session
// write (rename, activity) — all of which use time.Now().UTC(). A local
// default produced mixed-timezone timestamps in `ao session get`.
m.clock = func() time.Time { return time.Now().UTC() }
}
if m.lookPath == nil {
m.lookPath = exec.LookPath
Expand Down
19 changes: 19 additions & 0 deletions backend/internal/session_manager/manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,25 @@ func TestSpawn_AssignsIDAndGoesIdle(t *testing.T) {
t.Fatal("handle not folded")
}
}

// TestSpawn_StampsUTCTimestamps locks the default clock to UTC so spawn-stamped
Comment thread
greptile-apps[bot] marked this conversation as resolved.
// CreatedAt/UpdatedAt match every other session write (rename, activity), which
// all use time.Now().UTC(). A local default produced mixed-timezone timestamps
// in `ao session get` (created in local time, updated in UTC).
func TestSpawn_StampsUTCTimestamps(t *testing.T) {
m, st, _, _ := newManager()
if _, err := m.Spawn(ctx, ports.SpawnConfig{ProjectID: "mer", Kind: domain.KindWorker}); err != nil {
t.Fatal(err)
}
rec := st.sessions["mer-1"]
if loc := rec.CreatedAt.Location(); loc != time.UTC {
t.Fatalf("CreatedAt location = %v, want UTC", loc)
}
if loc := rec.UpdatedAt.Location(); loc != time.UTC {
t.Fatalf("UpdatedAt location = %v, want UTC", loc)
}
}

func TestSpawn_RollsBackOnRuntimeFailure(t *testing.T) {
m, st, _, ws := newManager()
m.runtime = &fakeRuntime{createErr: errors.New("boom")}
Expand Down
Loading