Does the TN3135 audio-session networking exception have a defined lifetime? Seeing a ~38.5 s revoke/re-grant cycle

TN3135 describes the exception that lets a watchOS app use low-level networking while it holds an active audio session. I have that working, and the app functions — but the network path is withdrawn and restored on a strikingly regular cycle, and I would like to know whether that is expected behaviour rather than something I am doing wrong.

Setup

  • Apple Watch Series 10 (Watch7,9), watchOS 26.5. Reproduced on a Series 6 (Watch6,2).
  • UIBackgroundModes: [audio]; AVAudioSession category .playAndRecord, mode .spokenAudio; activated with the async activate(options:completionHandler:).
  • NWConnection with NWProtocolWebSocket to a WebSocket relay over TLS.
  • The app streams 16 kHz mono PCM continuously while transmitting and holds the socket open otherwise.

Symptom

NWPathMonitor reports .unsatisfied, then .satisfied about two seconds later, over and over. Measured with the iPhone powered off, so the watch was on its own Wi-Fi:

Uptime between dropsOutage
36.4 s2.1 s
36.7 s1.9 s
36.9 s2.1 s

The regularity is what prompts the question — uptime varies by ±0.3 s and the outage is consistently 2.0 s. That reads as a timeout expiring rather than radio behaviour.

What I have ruled out

  • Not the network or the server. A browser client on the same relay, same TLS, same wire protocol, holds a WebSocket indefinitely.
  • Not the interface. Identical cadence over the companion ipsec1 tunnel with the iPhone present, and over the watch's own en0 with the iPhone powered off. Pinning requiredInterfaceType = .wifi while the iPhone is reachable fails outright — the path offers only ipsec1.
  • Not audio-session interruption. I observe interruptionNotification, routeChangeNotification, mediaServicesWereResetNotification and silenceSecondaryAudioHintNotification. None fire at a drop. At the moment the path goes .unsatisfied, the engine is running and the player node is actively playing.
  • Not session idleness. Playing continuous silence for the whole session, rather than only while reconnecting, made no difference — still 36.4 s.

The control that surprised me

To test whether this affects any long-lived watch socket or only audio-unlocked ones, I built a second app with no AVAudioSession at all, no audio background mode, holding a URLSessionWebSocketTask and kept alive by a WKExtendedRuntimeSession so screen sleep was not a factor.

It never connected. NWPathMonitor reported .unsatisfied once and never changed, across a 30 s run, and every request failed with "The Internet connection appears to be offline." I had expected URLSession to be permitted regardless.

Questions

  1. Does the audio-session networking exception in TN3135 have a defined lifetime, and is a periodic revoke/re-grant cycle expected?
  2. If so, is there a supported way to hold it continuously — or is the correct design simply to expect the interruption and reconnect through it?
  3. Is it expected that an app with no audio session gets no network path at all on watchOS, including via URLSession, even in the foreground with an extended runtime session?
Answered by DTS Engineer in 903685022

Thanks for sharing.

I’ve made sure that the folks looking at FB24377808 are up-to-speed on this. And with that, I’m gonna step out of this particular loop. If and when things change, I expect you’ll hear about it via FB24377808.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

One thing I cannot control for. Every observation is on a development-signed build with Developer Mode enabled, since that is the only way I can install on the watch — I do not yet have a distribution build to compare against. I am not aware of any mechanism by which that would revoke a network path periodically, and I have excluded an attached debugger (the measurements above were taken from the server side with no devicectl session attached and the paired iPhone powered off). But I cannot rule out Developer Mode itself. If that is the explanation, saying so would save me a lot of time.

A further data point. The system Walkie-Talkie app on the same watch does not exhibit this — it holds a session well beyond 38.5 s. Apple's documentation states it requires FaceTime and uses FaceTime Audio, so I assume it runs as a system VoIP service rather than through the TN3135 exception. That suggests the radio is perfectly capable of sustaining a connection, and the ~38.5 s cycle is a policy applied to the third-party path rather than a power or hardware limit. Given PushToTalk is iOS-only, is there any supported route to comparable behaviour on watchOS — or is a periodic reconnect the intended ceiling for third-party apps?

FYI, I’m still researching this and I’ll post an update here when I know more.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Thank you for researching - please let me know if there are any other data that I may be able to provide to home in on the issue

@danchapman, Please reproduce the problem, grab a sysdiagnose from the affected watch, and then file a bug with that sysdiagnose. Once you’re done, reply here with your bug number, and I’ll use that to continue my research.

Getting a sysdiagnose log from a watch is tricky. There are instructions on our Bug Reporting > Profiles and Logs page. Another option is to grab the log using devicectl. For example: [Striking out this half-formed thought — Quinn]

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

I have posted a report: FB24377808.

A follow-up with better data. I left the connection up overnight and logged every disconnect at the server — 1,549 samples over 19 hours.

Median session 38.5 s Mean 38.60 s, standard deviation 0.62 s (excluding outliers) 5th–95th percentile: 37.9 – 39.2 s 97.9% fall within 35–40 s; only 21 of 1,549 sit outside 30–50 s

Ninety percent of sessions land inside a 1.3-second window. The few long outliers (up to 311 s) are most likely idle or suspended periods.

The sysdiagnose attached to FB24377808 was captured inside this run, so the surrounding cycles are in it.

Are you repeatedly activating your audio session? The logs suggest that you are, and that’s what’s triggering this misbehaviour [1].

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] To be clear, it’s definitely misbehaviour, in that the system should ignore the redundant activate. We’ll use FB24377808 to track a fix for that.

Thanks — you were right that I was repeatedly activating. A retry timer was calling activate(options:completionHandler:) every 2 s during reconnects. I have stopped that: the session is now activated once and not re-activated while it stays active.

It has not resolved the problem, and the device console suggests the trigger may not be redundancy specifically. Every activate is followed by a revocation ~36.5 s later, including the first one after a fresh launch:

19:22:50  session activated      (first activate, fresh launch)
19:23:27  path: unsatisfied      -> 37 s
19:23:34  session activated
19:24:10  path: unsatisfied      -> 36 s
19:24:17  session activated
19:24:53  path: unsatisfied      -> 36 s
19:25:00  session activated
19:25:37  path: unsatisfied      -> 37 s

To rule out residual state across a restart, I repeated this with the app closed and no activation anywhere on the device for 7.5 minutes beforehand. The first activate after that quiet period behaves identically:

19:48:55  session activated      (no activate on the device since 19:41:09)
19:49:31  path: unsatisfied      -> 36 s

While revoked, the app cannot recover on its own. NWConnection fails on every attempt and NWPathMonitor stays .unsatisfied throughout. It turns .satisfied in the same second another activate runs — which then begins the next 36.5 s:

19:24:53  path: unsatisfied                     <- revoked
19:24:55  retry: dialling without re-activating -> POSIXErrorCode 50, Network is down
19:24:57  retry: dialling without re-activating -> POSIXErrorCode 50, Network is down
19:24:59  giving up on dial-only
19:25:00  session activated
19:25:00  path: satisfied                       <- restored, same second
19:25:00  connected

Calling setActive(false, options: .notifyOthersOnDeactivation) before re-activating makes no difference: 43.0 / 43.6 / 43.1 s drop-to-drop, against 42.6 / 42.9 / 43.0 / 42.2 s without it. The ~43 s is the 36.5 s plus my retry backoff.

So from app code there appears to be no way out — an activate is required for networking at all, every activate is followed by a revocation, and recovery requires another activate. Two questions if you are able:

  1. Is there a supported way to restore the networking exception without a second activate?
  2. Is a revocation ~36.5 s after any activate expected, or is that also part of what FB24377808 covers?

The sysdiagnose on FB24377808 was captured during exactly this behaviour (13:32:23 BST, 2026-08-17), so these cycles should be visible in it.

Configuration: Apple Watch Series 10 (Watch7,9), watchOS 23T570, development build, Xcode 26.6.

I have also built a minimal reproducer: a watch app that activates an audio session, opens one NWConnection to www.apple.com:443, and does nothing else — no microphone, no audio engine, no playback, no retry, no second activate, and no server of mine involved.

20:02:00  session activated
20:02:00  path: satisfied      connection: ready
20:02:35  path: unsatisfied    REVOKED 35.5s after activation
20:05:01  connection: failed(POSIXErrorCode 9: Bad file descriptor)

Because it never re-activates, it also answers whether the exception returns by itself: the path stayed .unsatisfied from 20:02:35 to 20:11:25 — 8m50s of repeated callbacks, never once .satisfied. It does not come back. An app that declines to re-activate stays offline permanently.

Happy to send the project if useful — it is about 100 lines.

It has not resolved the problem

Bummer.

Happy to send the project if useful

Please attach it to your bug (FB24377808), along with:

  • A sysdiagnose log taken after reproducing the problem with this test app
  • A rough timeline of what you saw

Once you’re done, reply back here and I’ll dig deeper.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Attached to FB24377808 alongside the reproducer project. All times BST (UTC+1), 2026-08-21. Apple Watch Series 10 (Watch7,9), watchOS 23T570.

The app is SessionProbe: it activates an AVAudioSession once, opens one NWConnection to www.apple.com:443, and does nothing else — no microphone, no audio engine, no playback, no retry, no second activate.

TimeWhat happened
20:57:57Tapped Start. AVAudioSession set to .playAndRecord / .spokenAudio and activated with the async activate(options:completionHandler:). This is the only activation in the entire run.
20:57:57NWPathMonitor: .unsatisfied.satisfied within the same second. Interfaces ipsec1, ipsec1, en0.
20:57:58NWConnection to www.apple.com:443 reaches .ready.
20:58:33NWPathMonitor: .satisfied.unsatisfied. 35.8 s after activation. Nothing was done by the app in between.
21:00:44The connection fails with POSIXErrorCode 9, Bad file descriptor.
20:58:33 – 21:04:57The path remains .unsatisfied for 6m24s, with repeated callbacks and not one .satisfied. The app never re-activates, so nothing prompts recovery — and none occurs.
21:03Sysdiagnose triggered, while still revoked.

Two things this run is intended to show:

  1. The revocation follows the first and only activation, ~36 s later, in an app that contains nothing else. No server, credential or wire protocol of ours is involved, and there is no microphone, audio engine or playback.
  2. The exception does not return on its own. Over 6m24s revoked, the path never became satisfied again. In our real app the only thing that ever restores it is another activate, which then begins the next ~36 s.

The attached sysdiagnose is sysdiagnose_2026.08.21_21-24-11, and this is the run it brackets. Times read from the app's own on-screen counters, since the debug console could not attach for this one:

Time (BST)What happened
21:23:05Start tapped, session activated — the only activation
21:23:38Path revoked — 33 s after activation
21:24:11Sysdiagnose triggered, 33 s into the revoked state
21:28:09Still revoked 4m31s after the revocation, no recovery

The watch log covers several hours, so the 20:57:57 / 20:58:33 run above should also be present in the same bundle.

Consistent with an earlier run of the same app: activation 20:02:00, revocation 20:02:35 (35.5 s), then .unsatisfied from 20:02:35 to 20:11:25 (8m50s) with no recovery.

So three runs of the reproducer: revocation at 35.5 s, 35.8 s and 33 s after the sole activation, and in every case the path never recovered on its own — the longest observed so far being 8m50s.

An update, in case it is useful to anyone else and to whoever picks up FB24377808.

We have a workaround. The revocation appears to be scheduled relative to the most recent activate(options:completionHandler:), and a subsequent activate replaces the pending revocation rather than adding another. So activating again before the deadline moves it, indefinitely.

Isolated in a minimal app — one .playAndRecord activate, one NWConnection to www.apple.com, an NWPathMonitor, and nothing else. Activating a second time 20 s in:

16:29:46  session activated (activate #1)
16:29:47  connection: ready
16:30:08  --- re-activating at 20s, without deactivating ---
16:30:08  session activated (activate #2)
16:30:44  path: unsatisfied
16:30:44  REVOKED — since each activate: #1:58.1s  #2:36.5s

The revocation lands 36.5 s after activate #2. The deadline implied by activate #1 — which would have been 16:30:22 — does not fire. Note also that the connection stayed ready across activate #2: the re-activation itself disturbs neither the path nor an open connection.

Renewing on a 30 s timer, the same app ran 140 s across four renewals with no revocation, where it had never previously survived 37 s. In our real app — live microphone, AVAudioEngine running, audio actually flowing — a watch held one WebSocket for 11 minutes 30 seconds with zero reconnections, while a second watch on the previous build reconnected roughly every 36 s throughout, on the same network and relay.

Two things I would still ask for:

  1. This is undocumented behaviour we are now depending on. Nothing promises that an activate supersedes a pending revocation, so a future watchOS release could change it and silently return us to a drop every 36 s. A supported way to hold the TN3135 networking grant — or the leniency fix — is still what we need.

  2. It may be worth checking whether this points at the cause. That the deadline is per-activate and replaceable suggests a single timer armed on activation, rather than an audit of session activity. If so, an app that never re-activates is guaranteed to lose networking at ~36.5 s no matter how correctly it behaves — which is the case we originally reported.

Happy to attach a sysdiagnose of the working case if that would help.

Thanks for sharing.

I’ve made sure that the folks looking at FB24377808 are up-to-speed on this. And with that, I’m gonna step out of this particular loop. If and when things change, I expect you’ll hear about it via FB24377808.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Does the TN3135 audio-session networking exception have a defined lifetime? Seeing a ~38.5 s revoke/re-grant cycle
 
 
Q