Troubleshooting
Practical problem, cause, and fix entries derived from the current implementation. Each fix references a concrete file, path, or message found in the repository.
Broker not running / connection refused
Symptom
CLI prints to stderr:
Could not reach AgentNotify: ...
Is the tray app running?
or for health:
Could not reach AgentNotify at http://127.0.0.1:47821: ...
Is the app running? Check the tray icon.
Source: src/AgentNotify.Cli/Program.cs.
Cause
The WPF tray process AgentNotify.Tray.exe owns the Kestrel host. It is not running, it failed during initialization, or the CLI is contacting the wrong port.
Fix
- Launch
AgentNotify.Tray.exefrom the Start menu or%LOCALAPPDATA%\Programs\AgentNotify. A second launch signals the existing tray process and exits (src/AgentNotify.App/App.xaml.cs). - Check
agentnotify.exe health. When a token is present it probesGET /v1/health; otherwiseGET /health(src/AgentNotify.Cli/Program.cs). - Confirm the port in
%LOCALAPPDATA%\AgentNotify\config.json(port, default47821) matches the port the CLI uses (--portorAGENTNOTIFY_PORT). - Inspect
%LOCALAPPDATA%\AgentNotify\logs\agentnotify-YYYYMMDD.logfor initialization failures. A failed startup terminates the incomplete tray process rather than leaving a partial listener (docs/ARCHITECTURE.md).
401 Unauthorized
Symptom
Error 401 Unauthorized: unauthorized
send exits 2; other commands exit 1.
Cause
The bearer token is missing or wrong. The API rejects any /v1/* request without Authorization: Bearer <token> where the token matches authToken via SHA-256 fixed-time comparison (src/AgentNotify.Api/Auth/TokenAuth.cs, src/AgentNotify.Api/ApiHost.cs). Token discovery order for the CLI (src/AgentNotify.Cli/Program.cs, src/AgentNotify.Core/Config/ConfigStore.cs):
--tokenflag for this invocation.AGENTNOTIFY_TOKENenvironment variable (whenapplyEnvOverrides: true).authTokenin%LOCALAPPDATA%\AgentNotify\config.json.
The config file is created lazily: ConfigStore.EnsureAuthToken generates 32 random bytes on first run and persists them (src/AgentNotify.Core/Config/ConfigStore.cs). Until the tray has run once no token exists. The CLI warning in that case is No token found. Has AgentNotify run at least once? Look at: {ConfigPath} and No auth token found at {ConfigPath}. Has AgentNotify run at least once? Set AGENTNOTIFY_TOKEN or pass --token. (src/AgentNotify.Cli/Program.cs, :459).
Fix
- Run the tray app at least once so the token is generated, then confirm:
agentnotify.exe token
The command reads the file without environment overrides (src/AgentNotify.Cli/Program.cs) and prints the raw token.
- Ensure the same token is used by the caller. Remove a stale
AGENTNOTIFY_TOKENfrom the environment or pass the correct value with--token. - When debugging with
curl, prefer:
TOKEN="$(agentnotify.exe token)"
curl -H "Authorization: Bearer $TOKEN" http://127.0.0.1:47821/v1/health
Do not print the token in logs or commit config.json.
agentnotify is not found after installation
Symptom
PowerShell or WSL reports command not found or agentnotify: command not found.
Cause
The installer adds %LOCALAPPDATA%\Programs\AgentNotify to the current user's PATH and creates Start menu shortcuts (docs/INSTALLATION.md). Existing shells still have the old PATH. WSL imports the Windows user PATH only when a new shell starts.
Fix
- Open a new PowerShell, Command Prompt, or WSL shell after installation.
- Verify:
Get-Command agentnotify.exe | Format-List
wsl -- which agentnotify.exe # inside WSL, use the .exe name
- The WSL wrapper is the Windows binary itself: call
agentnotify.exefrom WSL (docs/AGENT_INTEGRATION.md:91). No conversion ofconfig.jsonpaths is needed; the CLI reads%LOCALAPPDATA%\AgentNotify\config.jsonvia Windows APIs.
Rate limiting (429)
Symptom
POST /v1/notifications or POST /v1/events returns:
{ "error": "rate limit exceeded" }
with Retry-After: 1 and HTTP 429.
CLI send prints Error 429 TooManyRequests: rate limit exceeded and exits 1.
Cause
The API guards POST under /v1/notifications and the ARC /v1/events endpoint with one per-token
fixed-window counter. The limit is rateLimitPerSecond in config.json, default 30 requests per
second. When the count within the current one-second window reaches the limit, the middleware returns
429 with Retry-After: 1. The limiter is not a security boundary; it roughly bounds abusive
traffic.
Fix
- Back off for at least one second before retrying; honor the
Retry-Afterheader. - Batch or debounce agent sends rather than looping tightly. Use
keydeduplication to update a single active notification instead of creating many. - To change the limit, edit
%LOCALAPPDATA%\AgentNotify\config.jsonrateLimitPerSecondand restart the tray. Values<= 0are reset to30.
Request body too large (413)
Symptom
The broker rejects the request before routing. Depending on the client this surfaces as a Kestrel 413 Payload Too Large or a closed connection. Direct HTTP clients see no { "error": ... } body because the limit is enforced at the Kestrel layer (docs/API.md:134).
Cause
options.Limits.MaxRequestBodySize = config.MaxRequestBodyBytes (src/AgentNotify.Api/ApiHost.cs). Default 65536 bytes (AgentNotifyConfig.cs). Metadata is separately bounded to MaxMetadataBytes (8192 bytes serialized, src/AgentNotify.Core/Services/NotificationValidator.cs). Validation messages for fields include title must be at most 200 characters, message must be at most 4000 characters, and metadata must be at most 8192 bytes.
Fix
- Shorten
titleto at most 200 characters,messageto at most 4000, and keepmetadataat or below 8192 serialized bytes. - To allow larger bodies, increase
%LOCALAPPDATA%\AgentNotify\config.jsonmaxRequestBodyBytesand restart the tray. Values<= 0are reset to65536.
Notifications stored but no toast shown
Symptom
agentnotify list and the notification center show the notification as active, but no toast window appears. The log may contain [Toast] paused, not showing {id} {title} or [Toast] max visible (5) reached, queued {id}.
Cause
- Paused.
AgentNotifyConfig.PauseNotificationsistrue.ToastStackManager.Showreturns early and logs that line (src/AgentNotify.App/ToastStackManager.cs). The tray menu "Pause notifications" and Settings → General control this flag. - Visible limit reached.
MaxVisibleToasts(default5, 1–20) limits concurrent toasts. When reached, new notifications are queued in_pendingand shown when a visible toast closes (src/AgentNotify.App/ToastStackManager.cs). The log records the queue event.
DoNotDisturb does not suppress toasts; it only affects sounds (see Sounds not playing).
Fix
- Uncheck Pause notifications in the tray menu or Settings → General → "Pause desktop toasts (notifications are still stored)" and save.
- Increase Maximum visible toasts in Settings → Toasts if stacking is desired, or dismiss active toasts.
- Confirm that the notification status is
active(GET /v1/notifications?unresolved=true).dismissedandresolvedtoasts are closed onUpdate(src/AgentNotify.App/ToastStackManager.cs).
Sounds not playing
Symptom
No audible feedback on a new notification. The log may contain Configured sound file is missing: ... or Could not play sound ....
Cause
Sound is gated by NotificationSoundPolicy.ShouldPlay (src/AgentNotify.Core/Services/NotificationSoundPolicy.cs):
if (!soundsEnabled || pauseNotifications) return false;
return !doNotDisturb || (priority == critical && playCriticalSoundsDuringDoNotDisturb);
So sound is silent when any of these is true: soundsEnabled is false, pauseNotifications is true, or doNotDisturb is true without the critical-override for a critical notification. Additional causes: the configured file does not exist in %LOCALAPPDATA%\AgentNotify\sounds\, the file was not a valid .wav/.mp3 import, the import exceeded 10 MB, the volume is 0, or MediaPlayer failed to open the file.
The managed store resolves files with ManagedSoundStore.Resolve (src/AgentNotify.Core/Services/ManagedSoundStore.cs). Imported files are copied with a content-addressed safe name and must be .wav or .mp3 (src/AgentNotify.Core/Services/ManagedSoundStore.cs). Built-in tones chime.wav, ping.wav, alert.wav, knock.wav (src/AgentNotify.Core/Services/BuiltInTone.cs) are seeded from embedded resources on startup (src/AgentNotify.App/NotificationSoundService.cs).
Fix
- Enable Enable notification sounds in Settings → Sounds and set Volume (0–100) above
0. - If
Do Not Disturbis set, enable Allow critical sounds during Do Not Disturb for critical alerts, or clearDo Not Disturb. - Ensure
Pause desktop toastsis not checked — it suppresses both toasts and sounds. - Re-select the global or per-type sound in Settings → Sounds (built-in picker or Choose… for WAV/MP3). The preview button calls
Preview, which playsPath.GetFileName(fileName)through the same resolver (src/AgentNotify.App/NotificationSoundService.cs). - Check
%LOCALAPPDATA%\AgentNotify\logs\agentnotify-YYYYMMDD.logfor the missing/invalid media message; logging failures themselves never crash the broker (src/AgentNotify.Core/Logging/FileLogger.cs).
Outbound channel enabled but nothing delivered
Symptom
A notification is created and appears in history, but no outbound message arrives. The outbox counts in diagnostics remain Pending/Retry or stay 0.
Cause
Outbound delivery requires both a provider profile and a matching route to be enabled. NotificationDeliveryCoordinator.EnqueueAsync (src/AgentNotify.Core/Delivery/NotificationDeliveryCoordinator.cs) does no network I/O: it lists routes, builds the set of enabled provider IDs, then for each route where enabledProviderIds.Contains(route.ProviderId) && DeliveryRouting.Matches(route, notification) it enqueues an outbox item and signals the dispatcher. Disabled providers or disabled/mismatched routes produce 0 enqueued rows and the API response still succeeds.
DeliveryRouting.Matches (src/AgentNotify.Core/Delivery/DeliveryRouting.cs) requires:
route.Enabledistrueandnotification.Priority >= route.MinimumPriority.- When
route.TypeIdis set,NotificationTypes.Normalize(route.TypeId)equalsnotification.Type(case-insensitive). - When
route.Projectorroute.Agentis set, exact trimmed case-insensitive equality withnotification.Project/notification.Agent.
Routes also carry IncludeMessage; when false the payload redacts message (only title leaves the machine). Provider secrets are decrypted at the adapter boundary; missing or unreadable secrets produce provider_secrets_unreadable and the outbox item moves to DeadLetter after bounded retries. The dispatcher retries up to RetrySchedule.MaximumAttempts (6) with jittered backoff and a per-attempt timeout (DeliveryDispatcher.cs, default 15 seconds), then dead-letters. Attempt diagnostics are sanitized; exception text is never written to logs.
Fix
- In Settings → Channels enable the provider profile and the route. New profiles and routes begin disabled.
- Set the route filters so the notification matches: clear
TypeId/Project/Agentwhen a broad route is intended, and setMinimumPrioritylow enough. - Verify the route's Include notification message off-device setting when message content is required.
- Use the provider's Test action — it sends a fixed JSON payload through the same adapter with idempotency and bounded size checks (
src/AgentNotify.Core/Delivery/DeliveryDispatcher.cs). - Check provider credentials are re-entered correctly (blank password fields preserve the stored secret; the explicit removal checkbox deletes an optional credential).
Where the local log files live
Path
%LOCALAPPDATA%\AgentNotify\logs\ (ConfigStore.LogsDir, src/AgentNotify.Core/Config/ConfigStore.cs), surfaced in the tray menu as Open log folder (src/AgentNotify.App/App.xaml.cs).
Format
Daily files agentnotify-YYYYMMDD.log written by FileLogger (src/AgentNotify.Core/Logging/FileLogger.cs):
YYYY-MM-DD HH:mm:ss.fff [INFO|WARN|ERROR] message
Rotates by local date, AutoFlush: true, thread-safe, and tolerant — write failures are swallowed so logging never crashes the app (src/AgentNotify.Core/Logging/FileLogger.cs). ERROR entries include exception ToString() when an exception is supplied.
Open the current day's file to diagnose port conflicts, token generation, toast overrides, delivery dispatcher recovery (Recovered N interrupted outbound delivery item(s).), and sound file resolution.