RADIOCLI

Reliability

How RadioCLI handles ordinary internet-radio failure modes.

Internet radio fails in ordinary, boring ways. RadioCLI treats that as a product requirement.

Failure Modes

  • directory APIs can be down or rate limited
  • station metadata can be stale
  • resolved stream URLs can redirect or expire
  • stations can hang without returning an error
  • station codecs can be mislabeled
  • ICY metadata can be absent, malformed, or delayed
  • terminal dimensions can change mid-session
  • a data directory can become read-only, inaccessible, or full
  • a native service can fail even when its command is installed

Current Defenses

  • Radio Browser DNS service discovery and mirror fallback
  • sharded, bounded provider cache with per-entry corruption isolation and stale fallback
  • cached geotagged station atlas for Explore and Nearby, with local distance-first ranking
  • public HTTP deadlines that cover headers and response bodies
  • tune timeout
  • skip-to-next on failed tune when enabled
  • mpv IPC readiness check
  • TUI reattachment to a surviving playback owner, retaining controls, queue, listening history, and sleep timer without restarting the stream
  • decoded-audio readiness for ffplay, and audio-output plus decoder-buffer readiness for VLC; a surviving process or HTTP headers alone are insufficient
  • bounded reconnection after live mpv EOF or missing audio-clock progress, canceled by Stop or a replacement tune
  • explicit limited-control state when only ffplay fallback playback is available
  • AirPlay sender high-risk dependency gate, bounded Bonjour/Avahi discovery, bounded worker messages, passcode validation, and worker shutdown on startup timeout
  • playback-gated receiver animation and zero-signal visualizer frames for inactive playback states
  • metadata polling through mpv, avoiding a second stream listener
  • user-controllable approximate-location lookup, requested only when Nearby opens
  • required corrupt-library backups and separate best-effort cache recovery
  • locked, atomic local JSON writes with concurrent-process merge protection
  • nonfatal optional TUI history saves, with explicit errors for failed edits
  • defensive playlist import
  • functional compact navigation for tiny terminals
  • native per-user alarm registration on launchd, Windows Task Scheduler, and systemd Linux; occurrence locks make duplicate native dispatch idempotent
  • IANA-timezone recurrence, explicit missed-run grace, primary-station retry, optional fallback station, stop-after, and machine-local runtime health
  • authenticated loopback controls for alarms that began while no TUI was open
  • best-effort playback sleep inhibition and optional pre-alarm Guard

Network Requests

Fallback Radio Browser endpoints come from its current DNS service record instead of a fixed list of server names. Mirror attempts share a bounded deadline. If the native hostname lookup reports a temporary EAI_AGAIN failure, RadioCLI can retry through Node's DNS-protocol resolver. The original hostname remains in the HTTPS request and certificate check. This recovery is disabled whenever a proxy is configured so it cannot bypass the requested network route.

A response that sends headers but stalls while sending JSON cannot keep a request open indefinitely. Usable stale cache entries remain available after a request fails, and a failed cache write does not turn a successful response into a directory outage.

RadioCLI's public HTTP client supports HTTP(S) proxies through Node's native fetch. On Node 22.21 or newer in the 22 release line, or Node 24 or newer, set NODE_USE_ENV_PROXY=1 before starting RadioCLI along with HTTP_PROXY or HTTPS_PROXY; Node also reads NO_PROXY. A configured proxy without the required startup opt-in is reported instead of silently using a direct connection. See Node's proxy configuration documentation.

SOCKS proxies and ALL_PROXY as the sole proxy configuration are unsupported by this client and produce an actionable error. Proxy support leaves Node's TLS verification and certificate configuration unchanged. It does not configure the external player's networking. Authenticated local control uses literal loopback addresses and direct HTTP requests regardless of proxy settings.

SettingBehavior
RADIOCLI_OFFLINE=1Block RadioCLI's public Node HTTP requests, including directory, location, vote, and update requests; use available cached directory results and saved direct stream URLs
RADIOCLI_LOW_BANDWIDTH=1Use only a cached geotagged atlas for Explore and Nearby; smaller searches, health checks, location lookups, and manual controls remain available

A missing cache entry is reported rather than fetched in offline mode. These flags do not put the operating system or native player offline: a requested direct stream can still connect to its station. They do not download audio for offline listening.

Both flags skip automatic AirPlay receiver discovery. Offline mode also blocks manual discovery and reports the setting; low-bandwidth mode allows an explicit receiver refresh. Existing authenticated loopback controls and the stdio MCP transport remain available offline.

Library Write Failures

Library access errors preserve the existing bytes. An unreadable file is not renamed as corrupt or replaced with defaults, and an inaccessible current library does not trigger a switch to an older legacy file. If corrupt JSON cannot be backed up, RadioCLI leaves it intact and reports the backup failure.

Atomic replacement requires a writable library file and destination directory. A failed lock, write, permission change, file sync, or rename leaves the previous committed state intact. Cleanup errors cannot replace the original failure or make a completed replacement appear to have failed.

Each lock records a unique owner generation. A slow or suspended live writer does not lose ownership because of elapsed time. Only a positively dead owner or an explicitly released generation is reclaimed automatically. Unknown and legacy empty locks remain busy: close all RadioCLI processes before removing such a lock, and close older versions before upgrading. Keep the library on a local filesystem; shared filesystems and different hosts do not share PID ownership semantics.

The replacement file is synced before rename. RadioCLI also attempts to sync the parent directory after the commit; some operating systems and filesystems do not support directory sync. Atomic replacement and best-effort directory sync do not guarantee survival of every hardware or power failure.

When the library can be read but cannot be written, the TUI can still tune, navigate, stop, and quit. Optional history failures produce a library warning; they do not turn successful playback into a failed tune or prevent a sleep timer or alarm takeover from stopping it. Settings and favorite edits remain explicit saves and report failure without changing the saved state. Check the directory's permissions or choose a private writable location with RADIOCLI_HOME.

Playback Recovery And Secure Streams

Live directory stations reconnect after unexpected EOF or a stopped audio clock. Imported HTTP inputs qualify when they are nonseekable and have no known finite duration. Local files and known finite imported recordings stop at EOF. The stall deadline is at least 15 seconds and never shorter than the tune timeout. Paused playback does not trigger the watchdog.

Recovery uses at most three attempts with delays of 1, 2, and 4 seconds. Stop, station replacement, and alarm takeover cancel pending recovery. The retry budget resets after 60 seconds of uninterrupted, ready, unbuffered playback; brief reconnects cannot create an endless retry loop. Certificate, authentication, decoder, and output-device failures are not treated as transient transport errors.

RadioCLI passes --tls-verify=yes to mpv, including for redirected and nested streams. Native certificate stores and supported player CA configuration still apply. FFmpeg/ffplay receive -tls_verify 1 for network inputs. Plain HTTP media can need one compatibility restart because FFmpeg rejects an unused TLS option; that restart blocks both HTTPS and TLS protocols, including changed redirects.

Affected FFmpeg versions do not propagate certificate policy into HLS/DASH/IMF child requests. FFmpeg fallbacks therefore accept a restricted set of direct network media formats such as MP3, AAC, Ogg, FLAC and WAV. Use mpv for network playlists; for AirPlay, choose a direct audio stream or return to This device with mpv. Local ffplay inputs retain format support but cannot open network resources. This is an intentional security restriction on previously accepted inputs, not a claim of unchanged fallback format coverage.

There is also a known native TLS limitation: tested FFmpeg/OpenSSL builds, including mpv built against them, accept a trusted certificate with a mismatched numeric IP address. DNS hostname mismatch checks pass our fixtures. Prefer DNS station URLs; neither a separate preflight request nor these flags repair the native IP identity check. The release smoke reports this outcome explicitly.

Scheduled Radio Delivery

Saving an enabled alarm resolves its next occurrence and registers one native per-user job. The terminal can close afterward; the native scheduler starts a detached RadioCLI process, which validates the occurrence, plays the station, records the result, and registers the next run. Once authenticated local controls are ready, it opens the supported terminal emulator captured when the job was last synchronized and starts the RadioCLI TUI directly on the ringing screen. An already-running TUI is reused instead of opening a duplicate.

For Apple Terminal and iTerm2, saving or repairing an alarm performs a harmless Automation check while the user is present. macOS may ask whether Node can control the terminal; choosing Allow enables the later ringing window. Denying it does not prevent headless audio, and RadioCLI immediately reports that automatic terminal controls need attention instead of waiting until wake time. When a ringing TUI starts, it discovers active playback before native scheduler reconciliation and never unloads, deletes, or repairs the job currently carrying that audio.

PlatformNative integrationCatch-upWake behavior
macOSlaunchd LaunchAgentlaunchd can run after wake/loginNo exact-wake claim; the wake checkbox is not mapped to a privileged power event
WindowsTask Scheduler interactive-token taskStartWhenAvailableWakeToRun is requested when enabled, but hardware and power policy decide
Linux with systemd user sessionsystemd user timerpersistent timer catches up after the user session resumesNo WakeSystem claim without system privileges
Linux without systemd user schedulingUnsupportedNoneNone
BSD, Termux, and other experimental targetsNo verified alarm adapterNone claimedNone claimed

An installed scheduler executable alone is insufficient. Registration and diagnostics check the user service; a Linux host without a reachable systemd user manager cannot register reliable background alarms. RadioCLI does not substitute cron or a process-local timer for an unsupported native scheduler. The platform support matrix separates runtime eligibility from verified RadioCLI behavior.

Terminal reopening supports Apple Terminal, iTerm2, WezTerm, Ghostty, and kitty on macOS; Windows Terminal or a PowerShell console on Windows; and common GUI terminal emulators on Linux. Integrated terminals that cannot be launched independently fall back to the platform terminal where possible. If a graphical terminal is unavailable, audio still plays and opening radiocli manually reveals the authenticated ringing controls.

The Windows console launcher waits for its temporary PowerShell bootstrap to finish creating a separate console. Successful bootstrap completion still does not establish TUI readiness; the caller verifies the expected session. The console adapter requires PowerShell to permit .NET/native interop.

All three supported adapters require the computer to be powered on and the user to have a logged-in interactive audio session with usable local speakers. A powered-off machine cannot run RadioCLI. Exact behavior across ordinary sleep, hibernate, explicit sleep, a closed laptop lid, logout, locked sessions, battery policy, firmware timers, and audio-device policy cannot be guaranteed. Never use RadioCLI as the only alarm for safety-critical, medical, travel, or emergency timing.

Alarm Guard is different from an OS wake request. Guard starts now and holds an idle-sleep inhibitor until just after the next occurrence: caffeinate on macOS, a logind inhibitor via systemd-inhibit on Linux, or SetThreadExecutionState through PowerShell on Windows. It can improve delivery when the machine would otherwise idle-sleep, but it consumes battery or mains power and does not override shutdown, hibernate, explicit sleep, lid policy, logout, or other OS decisions. Guard state is machine-local and is reconciled again after each alarm change.

If an inhibitor helper exits unexpectedly, runtime health reports lost sleep protection while playback can continue. If Guard ownership cannot be verified, its record is retained for repair and its PID is not signaled. A failed native job removal is accepted only after the exact job is confirmed absent; otherwise the alarm definition and repair artifacts remain available. Deleting an alarm first saves it as disabled. If that save fails, deletion stops before native cleanup; if cleanup or the final save fails, the disabled definition remains available for repair or another deletion attempt. RadioCLI reports these cases as unresolved rather than claiming protection or cleanup succeeded.

Recurring alarms use minute precision, ISO weekdays, and an explicit IANA timezone. In a spring daylight-saving gap, RadioCLI uses the first valid minute later on that civil date. In a fall overlap, it fires the first matching instant only once. One-time alarms must be unambiguous absolute ISO-8601 minutes with an offset or Z. Resynchronize after changing the host timezone; recurring alarm definitions keep their own IANA timezone, while the platform job contains the already-resolved next instant.

When a native job starts late, the missed-run grace decides whether it still plays. An early launch does not mark the occurrence complete or consume its due dispatch. A due alarm tries its primary station twice, with a short bounded backoff, then tries the configured fallback. It stops after the requested number of minutes unless playback is transferred into the interactive TUI. True fade-in requires mpv; ffplay and VLC start at the target volume because they lack dependable runtime volume control. Scheduled audio is local-speaker only; unattended AirPlay is intentionally disabled.

Before starting the player, RadioCLI also checks the default local system output. If it is muted or below the alarm's configured volume, the runner attempts to unmute it and raise it to at least that level. Overlapping alarm runners share the original output state across library profiles: only the last runner to release it attempts restoration. Transferring playback to the interactive TUI preserves the current output level. A partial mixer change triggers a rollback attempt; a failed restore is reported and remains retryable. If every runner is forcibly terminated, recovery occurs on a later system-volume operation after any surviving mixer helper exits. An incomplete helper record blocks automatic adjustment and preserves the saved output state. This uses macOS output-volume scripting, Windows Core Audio through built-in PowerShell, or wpctl, pactl, or amixer on Linux. If the host exposes none of those controls, player volume still applies and runtime diagnostics report that the system output could not be raised.

If diagnostics report an unknown mixer helper after a crash, restart the computer before clearing native-write.json and the lock directory from .radiocli/system-volume under the OS account's home directory. Keep state.json: it contains the output state needed for the next recovery attempt.

Alarm Troubleshooting

Use the following in order:

radiocli alarm doctor
radiocli alarm status
radiocli alarm sync
radiocli alarm test ALARM_ID

doctor summarizes native scheduler support, unhealthy registrations, sleep inhibition, Guard processes, and active alarms. Add --json for a detailed report. status prints detailed runtime JSON. sync recreates registrations from the local definitions after a backup restore, host-timezone change, installation move, or scheduler failure. test makes sound: it executes a bounded preview of the alarm and stops if its safety deadline is exceeded.

For a source checkout, build before registration so the native job points at dist/cli.js. If the checkout or Node executable moves, rebuild and run radiocli alarm sync; doctor reports a missing executable or registration. Global npm and Homebrew installs provide the built command, but should still be resynchronized if an installation move invalidates its stored path.

An npm or source install runs scheduled work through the Node executable. macOS can therefore show node in its Background Activity notice even though the LaunchAgent belongs to RadioCLI. A trustworthy RadioCLI-branded notice requires a signed .app bundle with its helper registered through Service Management; the launchd label alone does not change executable identity.

Known Limits

  • Some stations play silence while still looking technically connected.
  • Radio Garden can be blocked by edge protection.
  • AirPlay support is experimental on macOS and explicitly opted-in native Linux; it depends on the local network, Bonjour/Avahi discovery, FFmpeg, and a loadable optional sender. Software RAOP checks do not establish every physical or AirPlay 2 receiver. Missing AirPlay receivers are output failures, not broken station streams.
  • Station health is local and opportunistic; there is not yet a long-term health score per station.
  • Native wake behavior is inherently best-effort and Linux scheduling currently requires a working systemd user manager.
  • The country-density map is symbolic. Explore uses a Natural Earth braille map with a movable scan cursor and true distance ranking, but it can only place stations that providers expose with valid coordinates.

See Architecture for the provider and playback boundaries.

On this page