fix(fileservice): don't hang on ListDirectory failures; don't leave zero-byte files on failed pulls (iOS 26.5.x app-group, #784) - #802
Conversation
…ero-byte file on failed pull
On iOS 26.5.x the device-side daemon rejects file operations on App Group
containers with RemoteServices error 11007 ("File paths cannot contain
'..'.") even for valid paths (issue #784, reproduced with Apple's own
devicectl). go-ios cannot fix the Apple bug, but it failed badly around it:
- `ios file ls` hung forever: ListDirectory blocked in
ReceiveOnClientServerStream while the device reported the failure on the
control (server->client) stream, so the error was silently lost.
ListDirectory now receives on both streams concurrently with a receive
timeout and surfaces failures as typed errors (*DeviceError, ErrTimeout)
instead of hanging. A pending control-stream read is tracked on the
Connection and consumed by the next control receive so the stream never
has two readers.
- A failed `ios file pull` left a zero-byte local file because os.Create
ran before PullFile. The pull path now removes the local file when the
download fails, so pipelines can't mistake a failed pull for a
successful empty file.
Adds a controlConnection interface seam so the control protocol is unit
tested against a fake XPC connection (control-stream error, unresponsive
device, happy path, pending-read handoff), plus tests for the pull
wrapper. Documents the iOS 26.5.x --app-group limitation in the CLI help.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J8eMENxJ1nec9CeHp4tjWk
|
/test-devices |
|
❌ Real-device tests failed — see run. |
pullToLocalFile promised no zero-byte or partial file is left behind on a failed pull, but only cleaned up when the pull callback returned an error. A Stat or Close failure after a seemingly-successful pull returned an error while leaving the incomplete file on disk. Close is exactly where buffered write errors such as a full disk surface, so this was the case most likely to leave a truncated file a pipeline could mistake for success. Move cleanup into a deferred, error-guarded os.Remove so every failure path after os.Create removes the file, and add a regression test for the post-pull finalize-failure path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J8eMENxJ1nec9CeHp4tjWk
|
Adversarial review (with a second-opinion pass via codex). Verdict: the core design is sound; I pushed one follow-up commit for a real cleanup gap and dismissed the rest as false positives or documented-by-contract. Fixed (commit
|
|
Thanks for tackling the #784 fileservice hang! Heads-up from a CI triage pass: the real-device e2e is failing here for a reason that looks like it needs another look at this change, not an infra flake.
That 30s timeout is the new code path this PR introduces in So the fix for the hang-on-failure case seems to have introduced a hang on the happy path. Could you take another look at the |
Add real-device e2e tests for the instruments FPS/network streaming commands (PR danielpaulus#806) and strengthen file ls coverage (PR danielpaulus#802), which previously had only device-free unit tests. - harness: StreamNDJSON runs a self-terminating (--duration-bounded) streaming command, asserts it exits on its own within a timeout (a command that hangs past --duration is a real bug, not killed away), and decodes each stdout line as a JSON object. - tunnel suite (iOS 17+): TestInstrumentsFPS asserts >=1 well-formed {"fps": <number>} sample; TestInstrumentsNetwork asserts >=1 {"type": <number>, "data": ...} envelope. Real samples are t.Log'd. - preios17 suite: same two commands over usbmuxd + DDI (no tunnel). - file ls: assert every entry is a non-empty filename string and require a non-empty listing for the crash-logs domain root. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J8eMENxJ1nec9CeHp4tjWk
Problem
On iOS 26.5.x, file operations against App Group containers (
--app-group) fail device-side with RemoteServices error 11007 "File paths cannot contain '..'." even though the paths contain no..(#784). That is an Apple daemon bug — reproduced identically with Apple's ownxcrun devicectlon the same devices — but go-ios failed badly around it:ios file ls --app-group=…hung forever with no output.ios file pullleft a zero-byte local file behind, which scripted pipelines can mistake for a successfully pulled empty file.Root cause
ListDirectorysendsRetrieveDirectoryListand then blocks inReceiveOnClientServerStream()with no timeout. Every other control response (CreateSession, RetrieveFile, ProposeFile) arrives on the server→client stream — when the device reports aRetrieveDirectoryListfailure there instead of sending the listing, the error was silently lost and the call blocked indefinitely.cmd_device_files.goranos.Create(localPath)beforePullFile, so any pull failure (including the device-side 11007 rejection, which happens before any data streaming) left a truncated 0-byte destination file.Fix
ios/fileservice:ListDirectorynow receives on both streams concurrently under a receive timeout (default 30s) and surfaces failures as typed errors:*fileservice.DeviceError(carries the device'sEncodedError+LocalizedDescription) andfileservice.ErrTimeout. A pending control-stream read left by a successful listing is tracked on theConnectionand consumed by the next control receive (createSession/PullFile/PushFile), so the control stream never has two concurrent readers.extractErrornow returns*DeviceErroreverywhere (same message format as before).cmd_device_files.go: the pull path is extracted into a testablepullToLocalFilewrapper that removes the local file when the download fails — no zero-byte/partial files remain. Success behavior (including pulling genuinely empty files) is unchanged.Connection.connis now a minimalcontrolConnectioninterface (satisfied by*xpc.Connection) so the control protocol is unit-testable without a device. No change to the wire protocol orios/xpc.ios file lshelp text inmain.gonow notes the iOS 26.5.x App Group limitation and the--appworkaround.Options considered
pendingControl), fully covered by tests.ios/xpcto support deadlines/contexts — cleaner long-term, but a much larger blast radius across all XPC consumers; out of scope for a targeted hardening.PullFile's writer contract and breaks empty-file pulls) vs. unlink on error (chosen) — minimal, and also cleans up partially-written files on mid-stream failures.Test plan
ios/fileservice/fileservice_test.goagainst a fake XPC connection: control-streamEncodedError→ typed*DeviceError, no hang; device never responds →ErrTimeoutwith short injected timeout; happy-path listing; pending control-read handoff toPullFile.cmd_device_files_test.go: erroring puller (immediate and mid-stream) → no local file remains; successful pull writes content and returns size.go build ./...,go test ./...,go test -raceon changed packages,gofmt -lclean.Improves #784 — the go-ios-side hang and zero-byte-file behaviors are fixed here, but the underlying App Group 11007 rejection is an Apple iOS 26.5.x daemon bug (Apple's devicectl fails identically), so the issue stays open to track that limitation.
🤖 Generated with Claude Code
https://claude.ai/code/session_01J8eMENxJ1nec9CeHp4tjWk