SagerNet / SagerNet/sing-box

[Android] VPNService may miss foreground-service deadline when started from Quick Settings tile (v1.14.0)

Open
#4,494 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Go
Stars
38.1k
Forks
4.6k
Avg merge
19d 15h
Merged PRs (30d)
1

Description

Operating system

Android (exact system version and device model are not included in the exported crash bundle)

Installation type

sing-box for Android graphical client

Graphical client version

1.14.0 (version code 730); core version 1.14.0; go1.26.7, android/arm64

Description

The Android client crashed while starting the VPN service from the Quick Settings tile.

The exported crash report contains:

android.app.RemoteServiceException$ForegroundServiceDidNotStartInTimeException:
Context.startForegroundService() did not then call Service.startForeground():
ServiceRecord{... u0 io.nekohasekai.sfa/.bg.VPNService}

The captured call path is:

TileService.onClick
-> TileService.toggleService
-> BoxService$Companion.start
-> VPNService

Crash bundle timestamp: 2026-09-03T04:21:34+0000.

This is an Android foreground-service startup failure. It is not a proxy-node or core network error; the crash occurs before the VPN service has successfully entered its normal running state.

Trigger / reproduction

Observed sequence:

  1. Configure a profile in the Android graphical client.
  2. Leave the VPN service stopped.
  3. Tap the sing-box Quick Settings tile to start the service.
  4. The process can crash with ForegroundServiceDidNotStartInTimeException.

The attached diagnostic export records the failure, but does not include the Android system version or device model. A deterministic reproduction rate is not yet known.

Source-level evidence

Auditing SagerNet/sing-box-for-android commit 5d5479d8bb60f7eb45e86402a8aa3a6131dd9ba3 (v1.14.0) shows the following ordering:

  • BoxService.kt:342-371: onStartCommand() registers a receiver, launches GlobalScope.launch(Dispatchers.IO), and returns Service.START_NOT_STICKY without calling startForeground().
  • BoxService.kt:96-101: the coroutine starts CommandServer first, including commandServer.start().
  • BoxService.kt:106-110: only after that does it enter startService() and call notification.show().
  • ServiceNotification.kt:84-99: this creates the notification channel and finally calls service.startForeground().
  • VPNService.kt:27-29: the Android service delegates onStartCommand() directly to BoxService.
  • TileService.kt:38-53: the stopped Quick Settings tile calls BoxService.start().

Therefore the effective path is:

startForegroundService()
-> onStartCommand()
-> asynchronous IO coroutine
-> CommandServer.start()
-> notification channel/building on Main
-> startForeground()

This deferred promotion can miss Android's foreground-service deadline, especially on automatic or system-triggered starts. The source explains the failure mode, but the crash export does not prove which individual operation consumed the deadline.

AndroidManifest.xml:128-133 declares VPNService with foregroundServiceType="systemExempted". The crash report does not contain a SecurityException or an explicit foreground-service-type permission error, so type eligibility is a secondary hypothesis rather than the primary observed failure.

Expected behavior

The service should promote itself to the foreground promptly after onStartCommand() begins, before asynchronous command-server/configuration initialization.

Related report

This appears related to SagerNet/sing-box#4114, which reports the same exception for io.nekohasekai.sfa/.bg.VPNService on Android 16 during Always-on VPN / BootReceiver startup. That issue was later closed automatically as stale without a maintainer diagnosis or confirmed fix.

Questions

Could the maintainers confirm whether the delayed startForeground() ordering is intentional, and whether the Android client should promote VPNService before starting CommandServer? If this is expected to be ROM-specific, please also clarify the supported foreground-service startup path for Android 16 and system-triggered VPN starts.

The original diagnostic archive is not attached because its configuration.json contains private proxy-node credentials; the crash stack and source evidence above are sufficient to identify the lifecycle path.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Read bg/BoxService.kt and follow VPNService.kt and TileService.kt to trace onStartCommand() through command-server startup and notification promotion. Inspect ServiceNotification.kt and AndroidManifest.xml for the foreground-service setup. Done means the VPN service reaches startForeground() promptly during Quick Settings and other system-triggered starts without breaking initialization.

Written by the indexing model from the issue text.

Assessment

Tech stack
android, kotlin
Domain
mobile-dev
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
62/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.