From 4089d75df0450ae43ffea62e750faf74fdac6d8c Mon Sep 17 00:00:00 2001 From: Peng Cheng Date: Sun, 11 Oct 2026 10:50:12 +0800 Subject: [PATCH] fix(macos): follow the log by name in dev-run.sh --log The app recreates ~/Library/Logs/LCTMac.log with FileManager.createFile(atPath:contents:) in applicationDidFinishLaunching, which replaces the file with a new inode. That usually happens after dev-run.sh has already started `tail -f`, so tail kept following the old, deleted file and --log never showed the new run's lines. Use `tail -F`, which reopens the path when the file is replaced. Co-Authored-By: Claude Opus 5.5 --- macos/Scripts/dev-run.sh | 7 +++++-- macos/Tests/LCTMacTests/DevRunTests.swift | 16 ++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/macos/Scripts/dev-run.sh b/macos/Scripts/dev-run.sh index 1caaf2e..8f8d771 100755 --- a/macos/Scripts/dev-run.sh +++ b/macos/Scripts/dev-run.sh @@ -13,7 +13,7 @@ # # Usage: Scripts/dev-run.sh [--no-launch] [--log] # --no-launch build & sign only, do not start the app -# --log after launching, tail -f ~/Library/Logs/LCTMac.log +# --log after launching, tail -F ~/Library/Logs/LCTMac.log set -euo pipefail @@ -162,7 +162,10 @@ if [ "$LAUNCH" -eq 1 ]; then echo "dev-run.sh: launched $APP_BUNDLE" if [ "$TAIL_LOG" -eq 1 ]; then echo "dev-run.sh: tailing $LOG_FILE (Ctrl-C to stop)" - tail -f "$LOG_FILE" + # -F, not -f: the app replaces the log file (new inode) when it + # finishes launching, which is usually after tail has started, and + # `tail -f` would keep following the old, deleted file. + tail -F "$LOG_FILE" fi else echo "dev-run.sh: build complete (--no-launch): $APP_BUNDLE" diff --git a/macos/Tests/LCTMacTests/DevRunTests.swift b/macos/Tests/LCTMacTests/DevRunTests.swift index 97a0f41..90e12f3 100644 --- a/macos/Tests/LCTMacTests/DevRunTests.swift +++ b/macos/Tests/LCTMacTests/DevRunTests.swift @@ -288,6 +288,22 @@ final class DevRunTests: XCTestCase { XCTAssertFalse(log.contains("open "), "--no-launch must not open the app, got: \(log)") } + func testDevRun_Log_FollowsLogFileByName() throws { + let stubs = try makeStubbedRun(securityOutput: nil) + try writeExecutable("tail", contents: """ + #!/bin/bash + echo "tail $*" >> "$LCT_STUB_LOG" + """, in: stubs.workDir.appendingPathComponent("bin")) + let result = try run("dev-run.sh", arguments: ["--log"], stubs: stubs, identity: "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF") + + XCTAssertEqual(result.exitStatus, 0, "dev-run.sh failed: \(result.stderr)") + let log = stubLog(stubs) + XCTAssertTrue( + log.contains("tail -F ") && log.contains("Library/Logs/LCTMac.log"), + "the app replaces its log file on launch, so --log must follow it by name (tail -F), got: \(log)" + ) + } + func testDevRun_RunningInstance_QuitsGracefullyWithoutPkill() throws { let pgrep = """ #!/bin/bash