Skip to content

Commit ca3cdff

Browse files
committed
feat: pid file writing and clearing and improved error handling for load state
1 parent a0f8bb5 commit ca3cdff

2 files changed

Lines changed: 57 additions & 9 deletions

File tree

lib/httpapi/server.go

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -350,16 +350,11 @@ func (s *Server) StartSnapshotLoop(ctx context.Context) {
350350
for {
351351
currentStatus := s.conversation.Status()
352352

353-
// Send initial prompt when agent becomes stable for the first time
353+
// Send initial prompt & load state when agent becomes stable for the first time
354354
if convertStatus(currentStatus) == AgentStatusStable {
355355

356356
if !s.stateLoadComplete && s.statePersistenceCfg.LoadState {
357-
_, err := s.conversation.LoadState(s.statePersistenceCfg.StateFile)
358-
if err != nil {
359-
s.logger.Warn("Failed to load state file", "path", s.statePersistenceCfg.StateFile, "err", err)
360-
} else {
361-
s.logger.Info("Successfully loaded state", "path", s.statePersistenceCfg.StateFile)
362-
}
357+
_, _ = s.conversation.LoadState(s.statePersistenceCfg.StateFile)
363358
s.stateLoadComplete = true
364359
}
365360
if !s.conversation.InitialPromptSent {
@@ -612,6 +607,11 @@ func (s *Server) Start() error {
612607
Handler: s.router,
613608
}
614609

610+
// Write PID file if configured
611+
if err := s.writePIDFile(); err != nil {
612+
return xerrors.Errorf("failed to write PID file: %w", err)
613+
}
614+
615615
return s.srv.ListenAndServe()
616616
}
617617

@@ -626,6 +626,9 @@ func (s *Server) Stop(ctx context.Context) error {
626626
}
627627
}
628628

629+
// Clean up PID file
630+
s.cleanupPIDFile()
631+
629632
// Clean up temporary directory
630633
s.cleanupTempDir()
631634

@@ -644,6 +647,43 @@ func (s *Server) cleanupTempDir() {
644647
}
645648
}
646649

650+
// writePIDFile writes the current process ID to the configured PID file
651+
func (s *Server) writePIDFile() error {
652+
if s.statePersistenceCfg.PidFile == "" {
653+
return nil
654+
}
655+
656+
pid := os.Getpid()
657+
pidContent := fmt.Sprintf("%d\n", pid)
658+
659+
// Create directory if it doesn't exist
660+
dir := filepath.Dir(s.statePersistenceCfg.PidFile)
661+
if err := os.MkdirAll(dir, 0o755); err != nil {
662+
return xerrors.Errorf("failed to create PID file directory: %w", err)
663+
}
664+
665+
// Write PID file
666+
if err := os.WriteFile(s.statePersistenceCfg.PidFile, []byte(pidContent), 0o644); err != nil {
667+
return xerrors.Errorf("failed to write PID file: %w", err)
668+
}
669+
670+
s.logger.Info("Wrote PID file", "pidFile", s.statePersistenceCfg.PidFile, "pid", pid)
671+
return nil
672+
}
673+
674+
// cleanupPIDFile removes the PID file if it exists
675+
func (s *Server) cleanupPIDFile() {
676+
if s.statePersistenceCfg.PidFile == "" {
677+
return
678+
}
679+
680+
if err := os.Remove(s.statePersistenceCfg.PidFile); err != nil && !os.IsNotExist(err) {
681+
s.logger.Error("Failed to remove PID file", "pidFile", s.statePersistenceCfg.PidFile, "error", err)
682+
} else if err == nil {
683+
s.logger.Info("Removed PID file", "pidFile", s.statePersistenceCfg.PidFile)
684+
}
685+
}
686+
647687
// HandleSignals sets up signal handlers for:
648688
// - SIGTERM, SIGINT, SIGHUP: save conversation state, then close the process
649689
// - SIGUSR1: save conversation state without exiting
@@ -664,6 +704,9 @@ func (s *Server) HandleSignals(ctx context.Context, process *termexec.Process) {
664704
}
665705
}
666706

707+
// Clean up PID file
708+
s.cleanupPIDFile()
709+
667710
// Now close the process
668711
if err := process.Close(s.logger, 5*time.Second); err != nil {
669712
s.logger.Error("Error closing process", "signal", sig, "error", err)

lib/screentracker/pty_conversation.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -442,22 +442,26 @@ func (c *PTYConversation) LoadState(stateFile string) ([]ConversationMessage, er
442442

443443
// Check if file exists
444444
if _, err := os.Stat(stateFile); os.IsNotExist(err) {
445+
c.cfg.Logger.Info("No previous state to load (file does not exist)", "path", stateFile)
445446
return nil, nil
446447
}
447448

448449
// Read state file
449450
data, err := os.ReadFile(stateFile)
450451
if err != nil {
452+
c.cfg.Logger.Warn("Failed to load state file", "path", stateFile, "err", err)
451453
return nil, xerrors.Errorf("failed to read state file: %w", err)
452454
}
453455

454456
if len(data) == 0 {
455-
return nil, xerrors.Errorf("failed to read state file: empty state file")
457+
c.cfg.Logger.Info("No previous state to load (file is empty)", "path", stateFile)
458+
return nil, nil
456459
}
457460

458461
var agentState AgentState
459462
if err := json.Unmarshal(data, &agentState); err != nil {
460-
return nil, xerrors.Errorf("failed to unmarshal state: %w", err)
463+
c.cfg.Logger.Warn("Failed to load state file (corrupted or invalid JSON)", "path", stateFile, "err", err)
464+
return nil, xerrors.Errorf("failed to unmarshal state (corrupted or invalid JSON): %w", err)
461465
}
462466

463467
c.InitialPromptSent = agentState.InitialPromptSent
@@ -470,6 +474,7 @@ func (c *PTYConversation) LoadState(stateFile string) ([]ConversationMessage, er
470474
c.firstStableSnapshot = c.cfg.FormatMessage(strings.TrimSpace(snapshots[len(snapshots)-1].screen), "")
471475
}
472476

477+
c.cfg.Logger.Info("Successfully loaded state", "path", stateFile, "messages", len(c.messages))
473478
return c.messages, nil
474479
}
475480

0 commit comments

Comments
 (0)