Commit 9d1e6dd

Eric Bower  ·  2026-05-16 11:02:50 -0400 EDT
parent 936c1fa
feat(wait): print last 20 lines of session history on failure
1 files changed,  +92, -2
+92, -2
......@@ -1585,12 +1585,38 @@ fn wait(cfg: *Cfg, matchers: std.ArrayList(SessionMatch)) !void {
15851585 }
15861586 if (session.task_exit_code.? > 0) {
15871587 try stdout.print("---\n", .{});
1588- try stdout.print("[{d}] failed task={s} exit_status={d}\n\n", .{
1588+ try stdout.print("[{d}] failed task={s} exit_status={d}\n", .{
15891589 session.task_ended_at.?,
15901590 session.name,
15911591 session.task_exit_code.?,
15921592 });
1593- try stdout.print("See the logs:\nzmx history {s}\nzmx attach {s}\n", .{ session.name, session.name });
1593+
1594+ // Fetch and print the last 20 lines of history for debugging
1595+ const history_lines: usize = 20;
1596+ const history_text = fetchHistory(alloc, cfg, session.name) catch null;
1597+ if (history_text) |text| {
1598+ defer alloc.free(text);
1599+ try stdout.print("\nLast {d} lines of {s} history:\n", .{ history_lines, session.name });
1600+
1601+ // Count lines and find the start of the last N lines
1602+ var total_lines: usize = 0;
1603+ var it = std.mem.splitScalar(u8, text, '\n');
1604+ while (it.next()) |_| {
1605+ total_lines += 1;
1606+ }
1607+
1608+ const skip = if (total_lines > history_lines) total_lines - history_lines else 0;
1609+ var current: usize = 0;
1610+ it = std.mem.splitScalar(u8, text, '\n');
1611+ while (it.next()) |line| {
1612+ if (current >= skip) {
1613+ try stdout.print("{s}\n", .{line});
1614+ }
1615+ current += 1;
1616+ }
1617+ }
1618+
1619+ try stdout.print("\nSee the logs:\nzmx history {s}\nzmx attach {s}\n", .{ session.name, session.name });
15941620 try stdout.flush();
15951621 }
15961622 }
......@@ -1713,6 +1739,70 @@ fn kill(cfg: *Cfg, session_name: []const u8, force: bool) !void {
17131739 try w.interface.flush();
17141740 }
17151741
1742+/// Fetch terminal history from a session socket, returning it as an allocated
1743+/// string. Caller owns the returned memory and must free it.
1744+fn fetchHistory(
1745+ alloc: std.mem.Allocator,
1746+ cfg: *Cfg,
1747+ session_name: []const u8,
1748+) ![]const u8 {
1749+ const socket_path = socket.getSocketPath(alloc, cfg.socket_dir, session_name) catch |err| switch (err) {
1750+ error.NameTooLong => {
1751+ socket.printSessionNameTooLong(session_name, cfg.socket_dir);
1752+ return error.NameTooLong;
1753+ },
1754+ error.OutOfMemory => return err,
1755+ };
1756+ defer alloc.free(socket_path);
1757+
1758+ var dir = try std.fs.openDirAbsolute(cfg.socket_dir, .{});
1759+ defer dir.close();
1760+
1761+ const exists = try socket.sessionExists(dir, session_name);
1762+ if (!exists) {
1763+ return error.SessionNotFound;
1764+ }
1765+
1766+ const fd = ipc.connectSession(socket_path) catch |err| {
1767+ if (err == error.ConnectionRefused) socket.cleanupStaleSocket(dir, session_name);
1768+ return err;
1769+ };
1770+ defer posix.close(fd);
1771+
1772+ const format_byte: u8 = @intFromEnum(util.HistoryFormat.plain);
1773+ const payload = [_]u8{format_byte};
1774+ ipc.send(fd, .History, &payload) catch |err| switch (err) {
1775+ error.BrokenPipe, error.ConnectionResetByPeer => return error.SessionUnresponsive,
1776+ else => return err,
1777+ };
1778+
1779+ var sb = try ipc.SocketBuffer.init(alloc);
1780+ defer sb.deinit();
1781+
1782+ var result = std.ArrayList(u8).initCapacity(alloc, 4096) catch return error.OutOfMemory;
1783+ errdefer result.deinit(alloc);
1784+
1785+ while (true) {
1786+ var poll_fds = [_]posix.pollfd{.{ .fd = fd, .events = posix.POLL.IN, .revents = 0 }};
1787+ const poll_result = posix.poll(&poll_fds, 5000) catch return error.Timeout;
1788+ if (poll_result == 0) {
1789+ return error.Timeout;
1790+ }
1791+
1792+ const n = sb.read(fd) catch return error.ReadFailed;
1793+ if (n == 0) break;
1794+
1795+ while (sb.next()) |msg| {
1796+ if (msg.header.tag == .History) {
1797+ try result.appendSlice(alloc, msg.payload);
1798+ return result.toOwnedSlice(alloc);
1799+ }
1800+ }
1801+ }
1802+
1803+ return error.NoHistoryResponse;
1804+}
1805+
17161806 fn history(cfg: *Cfg, session_name: []const u8, format: util.HistoryFormat) !void {
17171807 var gpa = std.heap.GeneralPurposeAllocator(.{}){};
17181808 defer _ = gpa.deinit();