fixes

ober

a7a4cf64a1cbd66d4f89ddb9ff3ce7dcd95fb419

diff --git a/docs/cli.md b/docs/cli.md
index 248ddd0..2989d3f 100644
--- a/docs/cli.md
+++ b/docs/cli.md
@@ -43,7 +43,7 @@ Parsed before any subcommand.
 
 | Subcommand | Purpose |
 |---|---|
-| `session list` · `session resume <id>` | List or resume saved sessions. |
+| `session list` · `session resume <id>` · `session restore <id>` | List saved sessions, or restore one into the TUI. Use `--no-tui` to force the line-mode REPL. |
 | `config` | Print the resolved provider, model, and key status. |
 | `keys …` | Manage the encrypted key store (see below). |
 | `serve …` | Run the JSONL agent server (stdio or TCP). See [remote.md](remote.md). |
diff --git a/docs/getting-started.md b/docs/getting-started.md
index 5a1ae02..400d697 100644
--- a/docs/getting-started.md
+++ b/docs/getting-started.md
@@ -165,6 +165,7 @@ Local providers (**Ollama**, **MLX**) need no key. See
 ```bash
 jcode                       # interactive line-mode REPL
 jcode --tui                 # full terminal UI (see docs/tui.md)
+jcode session resume <id>   # restore a saved session in the TUI
 jcode -p "summarise this repo and list the entry points"   # one-shot
 jcode --model claude-opus-4-20250514   # override the model for this run
 ```
diff --git a/src/jcode/provider/provider.ss b/src/jcode/provider/provider.ss
index 4d5acc9..f4f61fd 100644
--- a/src/jcode/provider/provider.ss
+++ b/src/jcode/provider/provider.ss
@@ -120,6 +120,9 @@
         (string-contains msg "HTTP read timed out")
         (string-contains msg "stream read timed out")
         (string-contains msg "stream closed before a terminal event")
+        (and (string-contains msg "not permitted on closed port")
+             (or (string-contains msg "tcp-connection-in")
+                 (string-contains msg "tcp-connection-out")))
         (string-contains msg "provider aborted the stream")
         ;; Some OpenAI-compatible gateways occasionally return HTTP 200 with
         ;; a truncated or whitespace-only body. No assistant response was
@@ -865,6 +868,36 @@
                 (vector-set! closed? 0 #t)
                 (guard (e [(i/o-error? e) (void)]) (close-port in))
                 (guard (e [(i/o-error? e) (void)]) (close-port out))))
+            (def (raise-stream-timeout!)
+              (error 'jcode-http-post-stream
+                (format "stream read timed out after ~as of silence (host: ~a)"
+                        timeout-secs host)))
+            (def (raise-stream-closed! e)
+              (error 'jcode-http-post-stream
+                (format "stream closed before a terminal event (host: ~a): ~a"
+                        host (err->string e))))
+            (def (guard-stream-read thunk)
+              (guard (e [#t
+                         (let ((msg (err->string e)))
+                           (if (string-contains msg "not permitted on closed port")
+                             (if (vector-ref timed-out? 0)
+                               (raise-stream-timeout!)
+                               (raise-stream-closed! e))
+                             (raise e)))])
+                (thunk)))
+            (def (read-stream-line-or-false)
+              (check-stream-abort!)
+              (guard-stream-read
+                (lambda ()
+                  (let ((c (peek-char in)))
+                    (touch!)
+                    (check-stream-abort!)
+                    (if (eof-object? c)
+                      #f
+                      (let ((line (port-read-line in)))
+                        (touch!)
+                        (check-stream-abort!)
+                        line))))))
             (fork-thread
               (lambda ()
                 (guard (e [#t (when (tracing?)
@@ -891,47 +924,30 @@
                 (check-stream-abort!)
                 (port-write-string out req)
                 (touch!)
-                (let* ((status-line (port-read-line in))
+                (let* ((status-line (guard-stream-read
+                                      (lambda () (port-read-line in))))
                        (status (parse-http-status status-line))
-                       (_headers (port-read-headers in)))
+                       (_headers (guard-stream-read
+                                   (lambda () (port-read-headers in)))))
                   (touch!)
                   (check-stream-abort!)
                   (unless (= status 200)
-                    (let ((body (read-http-body-lines
-                                  (lambda ()
-                                    (check-stream-abort!)
-                                    (let ((c (peek-char in)))
-                                      (touch!)
-                                      (check-stream-abort!)
-                                      (if (eof-object? c)
-                                        #f
-                                        (let ((line (port-read-line in)))
-                                          (touch!)
-                                          (check-stream-abort!)
-                                          line)))))))
+                    (let ((body (read-http-body-lines read-stream-line-or-false)))
                       (raise-http-error status body)))
                   ;; Read SSE lines until EOF or chunked terminator.
-                  ;; Filter HTTP chunked transfer-encoding size lines — see
+                  ;; Filter HTTP chunked transfer-encoding size lines -- see
                   ;; above (TLS branch) for details.
                   (let loop ()
-                    (check-stream-abort!)
-                    (let ((c (peek-char in)))
-                      (touch!)
-                      (check-stream-abort!)
-                      (unless (eof-object? c)
-                        (let ((line (port-read-line in)))
-                          (touch!)
-                          (check-stream-abort!)
-                          (cond
-                            ((equal? line "0") (void))
-                            ((chunk-size-line? line) (loop))
-                            (else (line-cb line)
-                                  (check-stream-abort!)
-                                  (loop)))))))
+                    (let ((line (read-stream-line-or-false)))
+                      (when line
+                        (cond
+                          ((equal? line "0") (void))
+                          ((chunk-size-line? line) (loop))
+                          (else (line-cb line)
+                                (check-stream-abort!)
+                                (loop))))))
                   (when (vector-ref timed-out? 0)
-                    (error 'jcode-http-post-stream
-                      (format "stream read timed out after ~as of silence (host: ~a)"
-                              timeout-secs host)))
+                    (raise-stream-timeout!))
                   status))
               (lambda ()
                 (vector-set! done? 0 #t)
diff --git a/src/jcode/ui/cli.ss b/src/jcode/ui/cli.ss
index 741f702..c19b950 100644
--- a/src/jcode/ui/cli.ss
+++ b/src/jcode/ui/cli.ss
@@ -139,6 +139,9 @@
          (tui-main args))
         ;; Commands and REPL
         ((null? rest)                   (interactive-mode opts))
+        ((and (session-restore-command? rest)
+              (not (assoc '--no-tui opts)))
+         (tui-main args))
         ((equal? (car rest) "session")  (session-command (cdr rest)))
         ((equal? (car rest) "config")   (config-command (cdr rest)))
         ((equal? (car rest) "keys")     (keys-command (cdr rest)))
@@ -218,6 +221,14 @@
            (not (member (car rest)
                         '("config" "keys" "proxy" "verified" "relay" "connect"))))))
 
+(def (session-restore-command? rest)
+  (and (pair? rest)
+       (equal? (car rest) "session")
+       (pair? (cdr rest))
+       (or (equal? (cadr rest) "resume")
+           (equal? (cadr rest) "restore"))
+       (pair? (cddr rest))))
+
 (def (init-tools . maybe-verified-only)
   (let ((verified-only? (and (pair? maybe-verified-only)
                              (car maybe-verified-only))))
@@ -259,7 +270,7 @@ OPTIONS:
     --provider       Provider to use (default: anthropic)
     -p PROMPT        One-shot prompt; everything after -p is the prompt
     --tui            Launch terminal UI mode
-    --no-tui         Force line-mode REPL (default)
+    --no-tui         Force line-mode REPL
     --no-mcp         Skip MCP server initialization
     --no-expert      Disable configured expert escalation for this process
     --repl-port [127.x.x.x:]N
@@ -274,7 +285,8 @@ OPTIONS:
 
 COMMANDS:
     session list     List all sessions
-    session resume   Resume a previous session
+    session resume   Resume a previous session in the TUI
+    session restore  Alias for session resume
     config           Show or edit configuration
     keys             Manage encrypted API key store
                      (init | list | add | remove | unlock |
@@ -1584,7 +1596,8 @@ EXAMPLES:
              (printf "~a  ~a  ~a~n"
                (session-id s) (session-title s) (session-created s)))
            sessions))))
-    ((equal? (car args) "resume")
+    ((or (equal? (car args) "resume")
+         (equal? (car args) "restore"))
      (if (null? (cdr args))
        (printf "Usage: jcode session resume <session-id>~n")
        (let ((session (session-load (cadr args))))
diff --git a/src/jcode/ui/tui.ss b/src/jcode/ui/tui.ss
index 47dabdf..d525985 100644
--- a/src/jcode/ui/tui.ss
+++ b/src/jcode/ui/tui.ss
@@ -246,7 +246,9 @@
     (let* ((w (tb-width))
            (h (tb-height))
            (state (make-fresh-state w h))
-           (session (session-create "New session")))
+           (resume-id (tui-initial-resume-id args))
+           (restored-session (and resume-id (find-session-by-prefix resume-id)))
+           (session (or restored-session (session-create "New session"))))
       (tui-log "tui-main: terminal ~ax~a, session=~a" w h (session-id session))
       (set! *dbg-state* state)
       (app-state-session-id-set! state (session-id session))
@@ -254,9 +256,30 @@
       (tui-log "tui-main: refresh-mcp-sidebar! (begin)")
       (refresh-mcp-sidebar! state)
       (tui-log "tui-main: refresh-mcp-sidebar! (done)")
-      ;; Welcome message
+      ;; Welcome / restored transcript
       (app-state-messages-set! state
-        (list (msg-block-system (format "jcode ~a — Type your message, /help for commands" *version*))))
+        (cond
+          (restored-session
+           (append
+             (messages->display-blocks
+               (session-get-messages (session-id restored-session)))
+             (list
+               (msg-block-system
+                 (format "Resumed session ~a -- ~a"
+                         (session-id restored-session)
+                         (session-title restored-session))))))
+          (resume-id
+           (list
+             (msg-block-system
+               (format "Session not found (or ambiguous prefix): ~a -- see /sessions"
+                       resume-id))
+             (msg-block-system
+               (format "jcode ~a -- Type your message, /help for commands"
+                       *version*))))
+          (else
+           (list (msg-block-system
+                   (format "jcode ~a -- Type your message, /help for commands"
+                           *version*))))))
       (tui-log "tui-main: reflow-all! (begin)")
       (reflow-all! state)
       (tui-log "tui-main: draw-all! (begin)")
@@ -272,6 +295,25 @@
       (close-tui-log!)
       (close-trace-log!))))
 
+(def (tui-initial-resume-id args)
+  (let loop ((args args))
+    (cond
+      ((null? args) #f)
+      ((and (equal? (car args) "session")
+            (pair? (cdr args))
+            (or (equal? (cadr args) "resume")
+                (equal? (cadr args) "restore"))
+            (pair? (cddr args)))
+       (caddr args))
+      ((member (car args)
+               '("--tui" "--verbose" "--debug" "-d" "--no-mcp" "--no-expert"))
+       (loop (cdr args)))
+      ((and (member (car args)
+                    '("--provider" "--model" "-m" "--repl-port" "--trace"))
+            (pair? (cdr args)))
+       (loop (cddr args)))
+      (else (loop (cdr args))))))
+
 (def (init-tools-for-tui)
   ;; Import and init tools — same as cli.ss
   (init-file-tools)
diff --git a/test/run.ss b/test/run.ss
index 5a34b5e..479f795 100644
--- a/test/run.ss
+++ b/test/run.ss
@@ -92,6 +92,10 @@
         (guard (e [#t e])
           (error 'stream-chat-with-expert
             "provider aborted the stream before producing a usable tool call"))]
+      [closed-tcp-port-error
+        (guard (e [#t e])
+          (error 'read-char
+            "not permitted on closed port #<input port tcp-connection-in>"))]
       [ordinary-error
         (guard (e [#t e])
           (error 'http-post-json "invalid request body"))])
@@ -101,6 +105,8 @@
           (provider-retryable-error? incomplete-stream-error) #t)
   (check! "provider-aborted model stream is retryable"
           (provider-retryable-error? provider-aborted-error) #t)
+  (check! "closed TCP stream port is retryable"
+          (provider-retryable-error? closed-tcp-port-error) #t)
   (check! "ordinary provider errors remain terminal"
           (provider-retryable-error? ordinary-error) #f))