Hive
Bare NSCocoaErrorDomain 3840 “Unable to parse empty data.” from empty x-tuist-warnings header and empty simctl JSON
GitHub issue · Open
What happened?
Two JSONSerialization call sites can surface a bare Error Domain=NSCocoaErrorDomain Code=3840 "Unable to parse empty data." with no hint of which step failed. Expected behavior: a typed Tuist error that names the failing step, or (for the header case) no error at all.
OutputWarningsMiddlewarethrows on an emptyx-tuist-warningsheader.cli/Sources/TuistHTTP/OutputWarningsMiddleware.swift:42-49(unchanged onmaintoday).Data(base64Encoded: "")returns an empty, non-nilData, so thecouldntConvertToDataguard passes andJSONSerialization.jsonObjectthrows 3840. Any server response that carries the header with an empty value would fail the request instead of being treated as “no warnings”.SimulatorController.devices()/runtimes()passsimctl list … --jsonstdout straight toJSONSerialization.cli/Sources/TuistCore/Simulator/SimulatorController.swift:147-150, 170-173. Ifsimctlexits 0 with empty stdout, the raw 3840 escapes andTuistCommandprints it verbatim.
Background: in April 2026 (Tuist 4.182.0, Xcode 26.4), tuist test <scheme> -d "iPhone 17" -o 26.4 run from an agent-sandboxed shell failed with only that 3840 string. The session log showed it right after the run-metadata upload, so it looked like an analytics//complete problem. TrackableCommand.run uploads metadata and then rethrows the command’s own error, so the error came from the command itself. The only public raw-JSONSerialization parse sites on that path are the two simctl ones above. JSONDecoder failures and OpenAPI client errors render differently, which rules them out. We could not re-trigger it on Xcode 27.0: with CoreSimulatorService unreachable, simctl now exits 1, and Tuist reports a clear terminated with the code 1 error. On Tuist 4.209.0 + Xcode 27.0 our tuist test passes. So item 2 is a probable cause, not a reproduced one.
Suggested hardening:
- Treat an empty
x-tuist-warningsvalue as no warnings. - In
devices()/runtimes(), map empty or non-JSON stdout to a typedSimulatorControllerErrorthat includes the command, exit status and stderr.
How do we reproduce it?
Item 1, without a server (the same decode the middleware performs):
import Foundation
let header = "" // empty x-tuist-warnings value
let data = Data(base64Encoded: header.data(using: .utf8)!)!
print("decoded bytes:", data.count)
do { _ = try JSONSerialization.jsonObject(with: data) } catch { print(error) }
swift repro.swift prints decoded bytes: 0, then the 3840 error. In the codebase, a unit test that feeds OutputWarningsMiddleware a response with x-tuist-warnings: "" shows the same thing.
Item 2: stub CommandRunning so simctl list devices --json returns exit 0 and empty stdout, then call SimulatorController().devices().
Error log
decoded bytes: 0
Error Domain=NSCocoaErrorDomain Code=3840 "Unable to parse empty data." UserInfo={NSDebugDescription=Unable to parse empty data.}
macOS version
27.0
Tuist version
4.209.0 (code sites unchanged on main as of 2026-10-01; original report on 4.182.0)
Xcode version
27.0 (27A266a); original report on 26.4
Investigate, reproduce, or fix this item in an isolated repository. Each Flight preserves its outcome and agent session.
Start a Flight and preserve its objective, outcome, and session here.
No GitHub comments yet.