v0.3.7 maintenance: CI Node.js 24 upgrade + 3 deferred features#25
v0.3.7 maintenance: CI Node.js 24 upgrade + 3 deferred features#25magicnight merged 7 commits intomainfrom
Conversation
Four tasks: Node.js 24 upgrade, MLX stdout capture, HF update detection, GUI/CLI PIDFile sharing. Independent, execute in any order. Recommended: 1 -> 4 -> 2 -> 3.
Silences the 'Node.js 20 deprecated' runner warning that surfaced during v0.3.6 release. Both major bumps are drop-in replacements — same inputs, same outputs, only the runtime changes.
dup2 STDOUT_FILENO and STDERR_FILENO to a Pipe at launch, tee each line to both the original fd (terminal visibility preserved) and LogManager.shared.debug(category:.system). Runs on a detached background Task, never blocks app lifecycle. Users reporting "model is slow" or "got a cryptic crash" can now see what mlx-swift-lm is printing without attaching a debugger. Install point is macMLXApp.init() BEFORE AppState is constructed — the engine's first load triggers the first MLX prints, so the redirect must be in place by then. @State(initialValue:) is the idiomatic way to defer AppState construction while keeping it as a @State var.
Write `.macmlx-meta.json` sidecar to every freshly-downloaded model directory capturing HF commit SHA + lastModified at download time. On Models-tab open, throttle-check each sidecar against the Hub (bounded to once a day). Rows whose head has advanced get an orange 'Update available' badge. Delete-and-re-download is the update action for now; a one-click refresh is a v0.4 follow-up. New unit tests: DownloadedModelMetaTests (3 cases) covering roundtrip, missing sidecar, corrupt sidecar.
Moves PIDFile from macmlx-cli to MacMLXCore so both targets read and write the same file. Record gains an Owner enum (.gui | .cli): - GUI startServer() writes a record tagged .gui on bind; stopServer() clears it. - CLI's `macmlx serve` now checks for a live PID with kill(pid, 0) and refuses to start when one exists, naming the owner so the user knows which process to shut down. - `macmlx ps` shows "Owner: GUI" or "Owner: CLI". Backward compat: decodes pre-v0.3.7 PID files (no owner key) as .cli so upgrading in place doesn't require cleanup. Full Ollama-style daemon mode (CLI commands proxied through the GUI's HTTP API) remains a later scope. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…pdate detection, shared PIDFile)
Swift 6 strict concurrency (default on in CI's Xcode 16.4) rejects 'static var installed' as non-concurrency-safe mutable global state. The flag is touched exactly once from macMLXApp.init() on the main thread before any other code runs, so nonisolated(unsafe) is the accurate annotation. Local dev build missed this because DerivedData was still cached from before StdoutCapture existed.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit c68e94a. Configure here.
| // Pipe's write fd is now duplicated to the original fd; close the | ||
| // pipe's side to avoid double-ownership. Foundation's Pipe retains | ||
| // its own reference so the fd stays live. | ||
| _ = close(writeFD) |
There was a problem hiding this comment.
Double-close of pipe write fd corrupts file descriptors
Medium Severity
The manual close(writeFD) on line 46 closes the pipe's write-side fd, but pipe.fileHandleForWriting still owns that fd number. When redirect() returns, pipe goes out of scope — only reader (the read-side FileHandle) is captured in the Task closure, not the Pipe object itself. So Pipe is deallocated, which releases fileHandleForWriting, whose deinit calls close() on the same fd number again. In a multi-threaded app, that fd may have been reused by another subsystem between the two closes, silently corrupting an unrelated file handle. The comment claiming "Foundation's Pipe retains its own reference so the fd stays live" is incorrect.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit c68e94a. Configure here.
| let envelope = try JSONDecoder.huggingFace.decode(ModelDetailsEnvelope.self, from: data) | ||
| let files = envelope.siblings.map { HFRemoteFile(path: $0.rfilename, size: $0.size, lfs: false) } | ||
| return (files, envelope.sha, envelope.lastModified) | ||
| } |
There was a problem hiding this comment.
New downloadMeta duplicates existing files method logic
Low Severity
downloadMeta(for:) is a strict superset of files(for:) — both hit the same /api/models/{id} endpoint, decode the same ModelDetailsEnvelope, and map siblings identically. files(for:) (still called by sizeBytes(for:)) could simply delegate to downloadMeta(for:).files, eliminating the duplicated URL construction, fetch, decode, and map logic. Maintaining two copies risks them drifting apart on future changes.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit c68e94a. Configure here.


Summary
This PR will grow as Tasks 2–4 land.
Test plan
Note
Medium Risk
Medium risk because it touches server lifecycle coordination (shared PID file + CLI preflight), adds new background stdout/stderr capture, and introduces network-based update checks that could affect UX or rate limits if misbehaving.
Overview
Bundles v0.3.7 maintenance work: CI/release workflows now use
actions/checkout@v5andactions/cache@v5to move off deprecated Node 20.Adds GUI↔CLI server coordination by moving
PIDFileintoMacMLXCore, tagging records with anowner(gui/cli), having the GUI write/clear the PID on server start/stop, and makingmacmlx serverefuse to start if a live PID exists;macmlx psnow prints the owner.Introduces MLX stdout/stderr capture into the Logs tab via a launch-time
StdoutCapture.install()(fd dup/pipe tee) and updates app initialization to install this beforeAppStateis created.Adds Hugging Face model update detection: downloads now write a
.macmlx-meta.jsonsidecar (DownloadedModelMeta) with commit/timestamp info,HFDownloadercan compare local meta to Hub head, and the Models UI periodically checks and shows an orange Update available badge.Reviewed by Cursor Bugbot for commit c68e94a. Bugbot is set up for automated code reviews on this repo. Configure here.