Hive Hive
Sign in

Bare NSCocoaErrorDomain 3840 “Unable to parse empty data.” from empty x-tuist-warnings header and empty simctl JSON

GitHub issue · Open

Metadata
Source
tuist/tuist #13748
Updated
Oct 1, 2026
Details

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.

  1. OutputWarningsMiddleware throws on an empty x-tuist-warnings header. cli/Sources/TuistHTTP/OutputWarningsMiddleware.swift:42-49 (unchanged on main today). Data(base64Encoded: "") returns an empty, non-nil Data, so the couldntConvertToData guard passes and JSONSerialization.jsonObject throws 3840. Any server response that carries the header with an empty value would fail the request instead of being treated as “no warnings”.
  2. SimulatorController.devices() / runtimes() pass simctl list … --json stdout straight to JSONSerialization. cli/Sources/TuistCore/Simulator/SimulatorController.swift:147-150, 170-173. If simctl exits 0 with empty stdout, the raw 3840 escapes and TuistCommand prints 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 (happy to open a PR with tests):

  • Treat an empty x-tuist-warnings value as no warnings.
  • In devices()/runtimes(), map empty or non-JSON stdout to a typed SimulatorControllerError that 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

Flights

Investigate, reproduce, or fix this item in an isolated repository. Each Flight preserves its outcome and agent session.

New Flights are paused Configure model inference, GitHub, and a sandbox provider to start another Flight. Existing results remain available below.
No Flights yet

Start a Flight and preserve its objective, outcome, and session here.

Comments

No GitHub comments yet.