diff --git a/pkg/agent/session.go b/pkg/agent/session.go index a6ca72d..4280d90 100644 --- a/pkg/agent/session.go +++ b/pkg/agent/session.go @@ -112,6 +112,23 @@ func (s *Session) saveTo(path, id, agentName, cwd string) error { } // saveToFull writes session state including backend/model info. +func sanitizeMessages(msgs []backend.Message) []backend.Message { + out := make([]backend.Message, 0, len(msgs)) + for _, m := range msgs { + for j, tc := range m.ToolCalls { + if len(tc.Arguments) > 0 { + var dummy json.RawMessage + if err := json.Unmarshal(tc.Arguments, &dummy); err != nil { + // Corrupted arguments — replace with empty object so save succeeds. + m.ToolCalls[j].Arguments = json.RawMessage("{}") + } + } + } + out = append(out, m) + } + return out +} + func (s *Session) saveToFull(path, id, agentName, backendName, modelName, cwd, remote string) error { ps := PersistedSession{ ID: id, @@ -120,7 +137,7 @@ func (s *Session) saveToFull(path, id, agentName, backendName, modelName, cwd, r Model: modelName, CWD: cwd, Remote: remote, - Messages: s.messages, + Messages: sanitizeMessages(s.messages), TaskState: s.TaskState, TotalInputTokens: s.TotalInputTokens, TotalCachedInputTokens: s.TotalCachedInputTokens, @@ -138,7 +155,12 @@ func (s *Session) saveToFull(path, id, agentName, backendName, modelName, cwd, r if err != nil { return fmt.Errorf("session save: %w", err) } - return os.WriteFile(path, data, 0600) + // Atomic write: write to temp file, then rename to preserve last good snapshot. + tmpPath := path + ".tmp" + if err := os.WriteFile(tmpPath, data, 0600); err != nil { + return fmt.Errorf("session save tmp: %w", err) + } + return os.Rename(tmpPath, path) } // LoadPersistedSession reads a PersistedSession from a JSON file.