Skip to content

Troubleshooting

This document is compiled from the backend source code (internal/, pkg/tag/), the AGENTS.md summary of business pitfalls, the FAQ documentation, and the error definitions and configuration logic of each module.

Table of Contents


Scanning Issues

Section sources: internal/services/scanner.go, internal/services/scan_progress.go, internal/services/auto_scan.go, AGENTS.md summary of business pitfalls

Files Not Found / Empty Scan Results

SymptomPossible CauseSolution
Song count is 0 after scanningThe music directory path is not configured or is incorrectSet the correct absolute path via PUT /api/v1/settings/music-path
Some files do not appearThe file format is not in the supported listCheck the SupportedFormats configuration in scan_config
Subdirectory files are missingThe directory was matched by an exclusion ruleCheck ExcludeDirs (matched by name) and ExcludePaths (exact path match)
Files not visible in the Docker environmentThe volume mount path is incorrectMount with an absolute path: -v /absolute/path/to/music:/app/music

Metadata Extraction Failure

Scanning relies on the pkg/tag pure Go library to extract audio metadata (no external dependencies). Optionally install ffprobe to obtain more accurate technical parameters (duration, bit rate, sample rate). The Docker image already includes ffprobe.

Common extraction failure scenarios:

  • Corrupted or incomplete files: truncated files caused by interrupted downloads or transfer errors
  • Non-standard tag formats: non-standard ID3v2 frames written by some software may fail to parse
  • Oversized embedded cover art: extremely large cover art images may cause a significant increase in memory usage

Supported Audio Formats

Default support: MP3, FLAC, WAV, APE, OGG, M4A, WMA, AIF/AIFF. This can be customized via the SupportedFormats field of the database configuration scan_config.

Title Rule Pitfalls

Scan title extraction follows these rules:

  1. There is a title field in the tag --> use tag.Title directly
  2. There is no title field in the tag --> use the filename with the extension removed

Note: no "longest common substring deduplication + concatenation" processing is done. A historical version once implemented this logic, which produced redundant titles like "Artist - Title", incorrectly mixing artist information into the title field.


Playback Issues

Section sources: internal/services/source/errors.go, internal/services/source/validator.go, AGENTS.md HLS radio proxy mode

Remote Song URL Expiration

The playback URL of a remote song may become invalid due to the source site's token expiring. The system handles this automatically through the source orchestration (Orchestrator) mechanism:

  1. The primary source URL returns a network error (NetworkError) or the downloaded file fails validation (InvalidAudioError)
  2. The system determines whether the error is fallbackable (the IsFallbackable function)
  3. If fallbackable, the Resolver fans out across plugins to search for alternative audio sources
  4. When all candidate sources fail, the terminal state AllSourcesFailedError is returned

Fallbackable error types:

  • InvalidAudioError: the file downloaded successfully but failed integrity validation (too short duration, too low bit rate, duration mismatch with expectation)
  • NetworkError: HTTP-layer failure (DNS/connection/timeout/non-2xx status code)
  • PluginInvocationError: the plugin music/url interface call failed

HLS Radio Cannot Play

SymptomCauseSolution
Radio playback fails outrightSource site Referer/UA hotlink protectionEnable HLS proxy mode
Radio not playing in Web embedded modeCORS restriction blockingEnable HLS proxy mode
The player selects the wrong MediaSourceThe URL lacks the .m3u8 suffixThe system already forces HLS radio URLs to carry the .m3u8 suffix

Enable HLS proxy mode:

bash
curl -X PUT http://localhost:58091/api/v1/settings/hls-proxy \
  -H "Authorization: Bearer TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"enabled": true}'

In proxy mode, all segments are relayed through the server, so mind the bandwidth cost. It is off by default (false), and should be enabled only when source site hotlink protection or CORS causes playback failure.

Audio Validation Failure

The Validate function of source orchestration validates file integrity after download; the following situations trigger a validation failure and an attempt to fall back:

Validation ReasonMeaningDefault Threshold
probe_failedffprobe / tag cannot read the file--
too_shortThe measured duration is below the absolute lower bound30 seconds
duration_mismatch_lowMeasured duration < expected x tolerance ratioexpected x 0.85
duration_mismatch_highMeasured duration > expected x upper limitexpected x 1.5
bitrate_too_lowThe average bit rate is too low8 kbps

Thresholds can be adjusted via the source_validation config key, or validation can be gradually disabled by setting enabled: false.


Authentication Issues

Section sources: internal/services/auth_service.go, internal/middleware/auth.go

Token Expiration

Token TypeValidityHandling
Access Token7 daysRefresh with the Refresh Token
Refresh Token30 daysLog in again to obtain a new one
Plugin internal token100 years (permanent)Automatically regenerated on process restart

Refresh the token:

bash
curl -X POST http://localhost:58091/api/v1/auth/refresh \
  -H "Content-Type: application/json" \
  -d '{"refresh_token": "YOUR_REFRESH_TOKEN"}'

Default Credentials

The default account and password are admin / admin. Ways to change them:

  • Docker: environment variables ADMIN_USERNAME / ADMIN_PASSWORD
  • Binary: command-line argument -password your_password

JWT Secret Loss Recovery

The JWT secret is stored in the database configs table (with the key jwt_secret) and is automatically generated as a 32-byte random secret during the initial migration. If the database is corrupted and the secret is lost:

  1. At startup, NewAuthService attempts to read jwt_secret from the database; on failure the service cannot start
  2. Recovery method: restore the database from a backup, or delete data/songloft.db to re-initialize (all user data and configuration will be lost)
  3. After the secret changes, all issued tokens automatically become invalid, and all clients need to log in again

Plugin Issues

Section sources: internal/jsplugin/health.go, internal/jsplugin/manager.go, AGENTS.md JS plugin section

Plugin Loading Failure

Common causes:

  • plugin.json has a format error or is missing required fields
  • The file SHA256 fingerprint validation fails (two-layer validation: manifest validation + runtime load validation)
  • QuickJS sandbox initialization failure (onInit script execution error)
  • Missing permission declaration: the permissions in the manifest does not declare the required capability (net, storage, fs:music, etc.)

Health Check Failure Causing Automatic Disabling

The plugin health checker performs a round of checks every 60 seconds, using a direct-to-VM probe (bypassing the scheduler's serial queue to avoid false positives caused by being blocked by a long fetch):

StatusMeaningHandling
healthyThe VM is alive and responding normallyReset the failure count
busyThe VM is processing a long request (holding the lock)Not counted as a failure; if still busy for 5 consecutive rounds (about 5 minutes), escalated to unhealthy
unhealthyThe VM cannot respond or the env has been destroyedAfter 3 consecutive failures, marked as error status and unloaded

Automatic recovery mechanism: a plugin marked as error enters an exponential-backoff recovery sequence: 1 minute --> 5 minutes --> 15 minutes --> 30 minutes --> 60 minutes. After a successful recovery, it automatically switches back to active status.

Manual recovery: trigger recovery manually via POST /api/v1/plugins/{id}/recover, which clears the backoff count and starts over.

Insufficient Permissions

Plugin runtime permissions are declared by the permissions field in the manifest, and internal/jsplugin validates them at runtime. If a plugin attempts an undeclared operation (such as calling songloft.net.udpBind without declaring the net permission), the request is rejected.

Solution: check the permissions array in the plugin's plugin.json and ensure it contains the required permissions.

Hot Reload Not Taking Effect

Plugins use file fingerprints (SHA256) to detect changes and automatically trigger hot reload. If an update does not take effect:

  1. Confirm that the file fingerprint of the new version has indeed changed
  2. Check the plugin directory's write permission
  3. Check the logs for any hot-reload-related error messages
  4. Manually disable and re-enable the plugin to force a reload

Idle Plugin Auto-Unloading

After a plugin is idle for more than 10 minutes, its VM is automatically unloaded to free resources (the database status stays active, and it is lazily reloaded on the next request). A plugin with a timer wakes up 2 minutes before the timer fires. Plugins with an active WebSocket connection or a running child process are not put to sleep.


Cache Issues

Section sources: AGENTS.md music cache section, internal/services/file_move.go

Insufficient Disk Space

The cache service uses an LRU eviction policy to manage disk space:

  • The default limit max_size is 1 GB; when exceeded, the least recently used files are evicted by last access time
  • Setting max_size=0 means the cache size is unlimited
  • View cache status: GET /api/v1/cache-manage/stats
  • Manual cleanup: POST /api/v1/cache-manage/clean
  • Adjust the limit: PUT /api/v1/cache-manage/config

Cache Directory Not Writable

Before setting a custom cache directory, use the validate-dir endpoint to verify it in advance:

bash
curl -X POST http://localhost:58091/api/v1/cache-manage/validate-dir \
  -H "Authorization: Bearer TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"cache_dir": "/mnt/data/music_cache"}'

This endpoint performs three checks: automatically create the directory, test writability, and return the remaining disk space.

Cross-Device rename Error (EXDEV)

Typical scenario: the temp file is created in the system /tmp (tmpfs), the target cache directory is mounted on a separate disk or Docker volume, and os.Rename returns syscall.EXDEV (cross-device link).

Solution: the system internally uses internal/services.moveFile(src, dst) uniformly instead of raw os.Rename. This function tries rename first and automatically falls back to copy + remove on EXDEV.

Note: pkg/tag's atomic write is not affected by this issue, because it uses os.CreateTemp(dir, ...) to create the temp file in the same directory as the source file, so rename is always within the same device.


Database Issues

Section sources: internal/database/sqlite.go, internal/database/errors.go, AGENTS.md database standards

SQLITE_BUSY Error

SQLITE_BUSY may occur during concurrent SQLite writes. The system already mitigates it through the following measures:

  • WAL mode: allows read/write concurrency, reads are not blocked by writes
  • busy_timeout(10000): waits up to 10 seconds when encountering a lock, avoiding an immediate SQLITE_BUSY
  • Connection pool limits: MaxOpenConns=10, MaxIdleConns=5

If SQLITE_BUSY still occurs, it is usually because a cross-table write did not correctly use a transaction:

# Correct approach: use RunInTx to obtain the UnitOfWork under the same *sql.Tx
db.RunInTx(ctx, func(ctx context.Context, uow *UnitOfWork) error {
    // uow.Songs / uow.Playlists share the same transaction
})

# Wrong approach: the service layer manually BeginTx (causes SQLITE_BUSY)

Migration Failure

Database migration uses goose, running goose.Up automatically at startup. Common causes of migration failure:

CauseSolution
The database file is lockedEnsure no other process is accessing the same database file
Migration SQL syntax errorCheck the corresponding SQL file in internal/database/migrations/
Insufficient disk spaceFree up disk space and restart
Manual ALTER caused schema inconsistencyRestore the database from a backup; manual ALTER data/songloft.db is prohibited

Error Semantics

The repository layer uses unified sentinel errors:

  • database.ErrNotFound: the record does not exist
  • database.ErrConflict: a UNIQUE constraint conflict or write conflict

The service layer should use errors.Is to distinguish them and translate them into business semantics.


Network Issues

Section sources: internal/services/whitelist.go, AGENTS.md HLS radio proxy mode / HTTP Proxy section

CORS Issues

In Web embedded mode, playing remote resources may encounter CORS restrictions. Solutions:

  • HLS radio: enable HLS proxy mode (PUT /api/v1/settings/hls-proxy), and the server proxies all requests
  • Remote songs: the cache service automatically caches the remote file locally, and after a cache hit, returns it via the same origin

SSRF Interception

The HLS proxy and related network features have built-in SSRF protection (services.IsHostnameAllowed), using a private network blocking strategy:

Blocked addresses:

  • localhost, *.local domains
  • Loopback addresses: 127.0.0.0/8, ::1
  • Private addresses: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, fc00::/7
  • Link-local: 169.254.0.0/16, fe80::/10
  • Unspecified addresses: 0.0.0.0, ::

When DNS resolution fails, the request is allowed through, letting the subsequent HTTP request report the error on its own.

The HLS proxy additionally performs same-origin validation (scheme + host + port strictly equal to song.URL), keeping non-same-origin URLs unrewritten to avoid becoming an open proxy.

HTTP Proxy Configuration

Configure the backend's outbound requests to be forwarded through an HTTP proxy:

bash
curl -X PUT http://localhost:58091/api/v1/settings/http-proxy \
  -H "Authorization: Bearer TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"proxy": "http://192.168.1.1:7890"}'

Supports HTTP/HTTPS/SOCKS5 proxy protocols. Takes effect immediately, no restart required.

Notes:

  • Loopback addresses (localhost/127.0.0.1/::1) automatically skip the proxy
  • Coexists with GitHub mirror acceleration (github_proxy URL prefix concatenation): first concatenate the mirror prefix, then forward through the HTTP Proxy
  • Scope of effect: plugin registry fetching, plugin download/update, system upgrade check/download

Tag Writing Issues

Section sources: pkg/tag/write.go, AGENTS.md tag writing section

Format Support Status

tag.WriteTag(filePath, opts) dispatches the write implementation by file extension:

FormatWrite MethodStatus
.mp3ID3v2.3 (TIT2, TPE1, TPE2, TALB, TYER, TCON, USLT, APIC)Supported
.flacVorbis Comment + PICTURE blockSupported
.apeAPEv2 tagSupported
.wavRIFF INFO / ID3Supported
.m4a / .mp4 / .m4biTunes-style atomsSupported
.ogg / .ogaVorbis Comment (Ogg container)Supported
.aif / .aiffID3v2.3 (ID3 chunk) + NAME/AUTH native chunksSupported
Other formats--Returns ErrUnsupportedWrite

Write Failure Handling

  • Unsupported formats return ErrUnsupportedWrite; the caller must degrade to a log entry and must not block the main flow
  • Writing uses an atomic strategy: first write to a temp file (created in the same directory as the source file), then os.Rename to overwrite the original file
  • When the atomic write fails (such as insufficient disk space or insufficient permissions), the original file remains unchanged

Platform-Specific Issues

Section sources: AGENTS.md platform adaptation pitfalls section, docs/faq.md

macOS

IssueCauseSolution
Token storage errorsecure_storage cannot use the Keychain in an unsigned sandboxThe system automatically degrades to SharedPreferences; no action needed
Upgrade check unavailableOnly Docker deployment supports online upgradeManually download and replace the new version

Android

IssueCauseSolution
Build failsSDK licenses not acceptedRun sdkmanager --licenses
Notifications not shownAndroid 13+ requires runtime notification permissionGrant the notification permission in the app settings
Killed in the background on HyperOS3The system aggressively reclaims the foreground serviceConfigure androidStopForegroundOnPause: false

Windows / Linux

IssueCauseSolution
Audio playback silent/crashingThe audio backend depends on libmpvInstall the libmpv library required by just_audio_media_kit
Cannot change the password on WindowsDouble-clicking the exe cannot pass argumentsCreate songloft.bat with songloft.exe -password your_password

Docker

IssueCauseSolution
Scheduled task time is wrongThe container time zone is inconsistent with the hostSet the environment variable TZ=Asia/Shanghai
Music files not visibleThe volume mount path is not an absolute pathUse -v /absolute/path:/app/music
Binary not updatedThe hot-replacement rule decided to keep the data versionSee the Docker hot-replacement rules table

Performance Tuning

Section sources: internal/database/sqlite.go, AGENTS.md music cache section

SQLite Optimization Parameters

At startup, the system automatically configures the following optimizations via DSN parameters, which usually need no manual adjustment:

ParameterValueEffect
journal_modeWALRead/write concurrency, reads not blocked by writes
busy_timeout10000 (10s)Wait on a lock instead of failing outright
synchronousNORMALAlready safe enough in WAL mode, reduces fsync
cache_size10000Page cache about 40 MB
foreign_keys1Enable foreign key constraints
MaxOpenConns10Connection pool upper limit
MaxIdleConns5Idle connection upper limit
ConnMaxLifetime30 minMaximum connection lifetime

Scanning Large Numbers of Files

Recommendations for scanning a large-scale music library:

  • Use ExcludeDirs to exclude non-music directories (such as .git, node_modules, thumbnail directories)
  • Use ExcludePaths to exclude specific paths precisely
  • Scanning is executed asynchronously; you can view the status via the progress API and can also cancel an in-progress scan
  • After the first scan, incremental scan speed improves significantly

Cache LRU Eviction Policy

Configuration ItemDefaultDescription
max_size1 GBTotal cache size upper limit; 0 means unlimited
cache_dir{data_dir}/music_cache/Can be customized to any absolute path

When switching the cache directory, the LRU index is automatically rebuilt, but old files are not migrated. The inflight deduplication mechanism ensures concurrent requests for the same song are downloaded only once.


Debugging Tips

Section sources: internal/handlers/log.go, internal/services/source/metrics.go, internal/jsplugin/health.go

Runtime Log Level

Supports dynamically switching the log level at runtime, no restart required:

bash
# View the current level
curl http://localhost:58091/api/v1/settings/log-level \
  -H "Authorization: Bearer TOKEN"

# Switch to the debug level
curl -X PUT http://localhost:58091/api/v1/settings/log-level \
  -H "Authorization: Bearer TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"level": "debug"}'

Supported levels: debug, info (default), warn, error. Changes take effect immediately and are persisted to the database, automatically restored after a restart.

Access Logs

The backend uses the standard library slog to output structured logs. When troubleshooting, it is recommended to temporarily switch to the debug level to get detailed request-handling information, then switch back to info after troubleshooting to reduce log volume.

Plugin Health Check API

View the health status of all plugins:

bash
curl http://localhost:58091/api/v1/plugins/health \
  -H "Authorization: Bearer TOKEN"

Returns each plugin's health classification (green/yellow/red), success rate, sample count, and recent failure records.

Source Orchestration Metrics API

View the Fetch success rate and health of each audio source plugin:

bash
curl http://localhost:58091/api/v1/plugins/health \
  -H "Authorization: Bearer TOKEN"

The returned PluginHealthSnapshot contains:

FieldDescription
classHealth classification: green (success rate >=80%) / yellow (intermediate state or insufficient samples) / red (success rate <40%)
success_rateSuccess rate of the most recent 200 attempts
samplesCurrent sample count (when <10, it will not be judged as red, avoiding false kills on cold start)
last_failuresRecent failure records and reasons (network_fail / probe_fail / validation_fail / plugin_invocation_fail)

Health affects the Resolver's ranking weight: score = baseScore * (0.3 + 0.7 * successRate). When samples are insufficient, a neutral value of 0.8 is used, so cold-start plugins are not given zero priority.

Swagger Documentation

After starting in development mode (make run), visit http://localhost:58091/swagger/index.html to view the interactive API documentation. The production version does not include Swagger.

pprof Profiling

The development mode build (-tags dev) includes pprof endpoints, which can be used for CPU/memory/goroutine profiling. This feature is unavailable in the production version.