Skip to content
Merged
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
16 changes: 13 additions & 3 deletions internal/cmd/ps.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,25 @@ func runPs(cmd *cobra.Command, args []string) error {

// Create tabwriter for aligned output
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
_, _ = fmt.Fprintln(w, "ID\tPROJECT\tSTATUS\tSTARTED")
_, _ = fmt.Fprintln(w, "--\t-------\t------\t-------")
_, _ = fmt.Fprintln(w, "ID\tPROJECT\tSTATUS\tTIMEOUT\tEXIT REASON\tSTARTED")
_, _ = fmt.Fprintln(w, "--\t-------\t------\t-------\t-----------\t-------")

for _, session := range sessions {
started := session.StartedAt.Format("2006-01-02 15:04:05")
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\n",
timeout := session.Timeout
if timeout == "" {
timeout = "-"
}
exitReason := session.ExitReason
if exitReason == "" {
exitReason = "-"
}
_, _ = fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\n",
session.ID,
session.ProjectDir,
session.Status,
timeout,
exitReason,
started,
)
}
Expand Down
36 changes: 34 additions & 2 deletions internal/cmd/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"os"
"path/filepath"
"sync/atomic"
"time"

"github.com/faize-ai/faize/internal/changeset"
Expand Down Expand Up @@ -269,6 +270,17 @@ func runStart(cmd *cobra.Command, args []string) error {
}
Debug("VM started successfully")

// Timeout enforcement: stop the VM when the timeout expires
var timedOut atomic.Bool
if timeoutDuration > 0 {
timer := time.AfterFunc(timeoutDuration, func() {
timedOut.Store(true)
fmt.Printf("\nSession timeout (%s) reached. Stopping...\n", timeoutDuration)
_ = manager.Stop(sess.ID)
})
defer timer.Stop()
}

// Take pre-snapshots of rw mounts for change tracking
type mountSnapshot struct {
source string
Expand Down Expand Up @@ -312,8 +324,28 @@ func runStart(cmd *cobra.Command, args []string) error {

// Attach to console — session stops when we return
fmt.Println("Attaching to console... (~. to detach)")
if err := manager.Attach(sess.ID); err != nil && !errors.Is(err, vm.ErrUserDetach) {
return fmt.Errorf("console error: %w", err)
attachErr := manager.Attach(sess.ID)
if attachErr != nil && !errors.Is(attachErr, vm.ErrUserDetach) {
return fmt.Errorf("console error: %w", attachErr)
}

// Determine exit reason and persist session metadata
exitReason := "normal"
if timedOut.Load() {
exitReason = "timeout"
} else if errors.Is(attachErr, vm.ErrUserDetach) {
exitReason = "detach"
}
now := time.Now()
sess.Timeout = startTimeout
sess.StoppedAt = &now
sess.ExitReason = exitReason
sess.Status = "stopped"
store, storeErr := session.NewStore()
if storeErr == nil {
if saveErr := store.Save(sess); saveErr != nil {
Debug("Failed to save session: %v", saveErr)
}
}

// Post-session change tracking
Expand Down
21 changes: 12 additions & 9 deletions internal/session/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,16 @@ type VMMount struct {

// Session represents a VM session with its configuration
type Session struct {
ID string `json:"id"`
ProjectDir string `json:"project_dir"`
Mounts []VMMount `json:"mounts"`
Network []string `json:"network"`
CPUs int `json:"cpus"`
Memory string `json:"memory"`
Status string `json:"status"` // "created", "running", "stopped"
StartedAt time.Time `json:"started_at"`
ClaudeMode bool `json:"claude_mode"` // Whether using Claude rootfs
ID string `json:"id"`
ProjectDir string `json:"project_dir"`
Mounts []VMMount `json:"mounts"`
Network []string `json:"network"`
CPUs int `json:"cpus"`
Memory string `json:"memory"`
Status string `json:"status"` // "created", "running", "stopped"
StartedAt time.Time `json:"started_at"`
ClaudeMode bool `json:"claude_mode"` // Whether using Claude rootfs
Timeout string `json:"timeout,omitempty"` // e.g., "2h" - human-readable timeout
StoppedAt *time.Time `json:"stopped_at,omitempty"`
ExitReason string `json:"exit_reason,omitempty"` // "normal" | "timeout" | "detach" | "killed"
}
Loading