Troubleshooting
Common problems, what causes them, and how to fix them. Most issues fall into one of three buckets: stale state from a previous install, the daemon not actually running, or the portal being on a different port than you think.
Start here: ideviewer status
Before anything else, run:
ideviewer status
It prints the three facts every other question depends on — which configuration file is in force, whether the daemon is alive, and whether the portal is reachable and accepting the customer key:
=== IDEViewer Status ===
Version: 0.7.0
Platform: windows/amd64
Configuration
[active] C:\ProgramData\IDEViewer\config.json
[shadowed, ignored] C:\Users\you\AppData\Local\IDEViewer\config.json
[absent] C:\Users\you\.ideviewer\config.json
More than one configuration is present. Only the first is used;
the others are leftovers from an earlier install. Clear them with:
ideviewer reset --config
Portal URL: http://localhost:8090
Customer key: 1a2b3c4d...ef01
Host token: xxxxxxxx...xxxx
Scan interval: 30 minutes
Enforcement: verified
Pinned keys: 1
Daemon
PID file: C:\Users\you\AppData\Local\IDEViewer\ideviewer.pid
Running (PID 14820)
Log file: C:\Users\you\AppData\Local\IDEViewer\logs\daemon.log
Last written: 2026-08-28 09:14:02 (48213 bytes)
Portal
Reachable
Customer key valid: Engineering Laptops
Command signing: configured
Anything marked [shadowed, ignored], Not running, Unreachable, or rejected points at the matching section below.
Which port is the portal on?
One number: 8090. It is the default for every way of running the portal, and PORTAL_PORT overrides all of them:
| How you start it | URL |
|---|---|
./start.sh | http://localhost:8090 |
./start.sh --docker | http://localhost:8090 |
PORTAL_PORT=9000 ./start.sh | http://localhost:9000 |
python run.py --port 9000 | http://localhost:9000 |
Earlier releases used 5000 for the local server while Docker published 8090 and the startup banner claimed 8080 — three numbers for one service. If you have an older portal/.env pinning PORTAL_URL=http://localhost:5000, update it or delete the line so the default applies.
5000 is a poor default on macOS in any case: AirPlay Receiver binds it, so the portal either fails to start or you reach Apple’s service instead. Turn AirPlay Receiver off in System Settings → General → AirDrop & Handoff if you deliberately want port 5000 back.
Whatever port you land on, register against the same one:
ideviewer register --customer-key YOUR-KEY --portal-url http://localhost:8090
“Customer key” returns HTTP 500
Two distinct causes, both now reported honestly rather than as a generic 500.
The portal has no command-signing key
If the portal runs with FLASK_CONFIG=production and neither COMMAND_SIGNING_PRIVATE_KEY nor COMMAND_SIGNING_PRIVATE_KEY_FILE is set, key validation used to fail with a 500 — the very first call a new daemon makes. Enrolment and scanning no longer depend on signing: registration succeeds, ideviewer status reports Command signing: not configured on the portal, and only enforcement commands are withheld (/api/signing-key and /api/enforcement-actions/pending return 503 SigningUnavailable).
To enable signing, generate a key and set it before starting the portal:
python3 -c "import base64,os;print(base64.b64encode(os.urandom(32)).decode())"
export COMMAND_SIGNING_PRIVATE_KEY='<the base64 value>'
Keep this stable across restarts. A new key invalidates the key every enrolled daemon has pinned; they re-pin at their next enrolment, but until then they will reject enforcement commands.
The daemon is using ids from a database that no longer exists
If you rebuilt or reset the portal database but the daemon still holds its old config.json, it references scan-request and enforcement-action ids that are gone. Those are 404s. Older portals returned them as 500 {"error": "Internal server error"}, because the API blueprint’s catch-all error handler also swallowed ordinary HTTP errors. Now you get a real 404 (and malformed request bodies get a real 400), which makes the fix obvious:
ideviewer reset --config
ideviewer register --customer-key YOUR-KEY --portal-url http://localhost:8090
Updating and cleaning up
ideviewer update on Windows did nothing
The installer cannot replace ideviewer.exe while the daemon has it open. ideviewer update now stops the daemon first, installs, and restarts it. If it reports that it could not stop the daemon:
ideviewer stop
ideviewer status # confirm "Not running"
ideviewer update
If ideviewer stop itself is not available (an old build), end the process in Task Manager, or:
taskkill /F /IM ideviewer.exe
schtasks /Delete /F /TN "IDEViewer Daemon"
Reinstalling didn’t give me a clean slate
IDEViewer loads its configuration from the first of these that exists, in priority order:
| Priority | Windows | macOS | Linux |
|---|---|---|---|
| 1 (system) | C:\ProgramData\IDEViewer\config.json | /Library/Application Support/IDEViewer/config.json | /etc/ideviewer/config.json |
| 2 (user) | %LOCALAPPDATA%\IDEViewer\config.json | ~/.ideviewer/config.json | ~/.ideviewer/config.json |
| 3 (legacy) | %USERPROFILE%\.ideviewer\config.json | (same as 2) | (same as 2) |
ideviewer register writes the user path. A leftover file at the system or legacy path therefore wins, and the daemon keeps using the previous install’s portal URL, customer key and host token — registration looks like it did nothing. register now warns when this is the case, and ideviewer status marks the losers [shadowed, ignored].
Clear everything a reinstall would inherit:
ideviewer reset --config # every configuration file (the usual fix)
ideviewer reset --all # configuration, logs, git hooks, quarantine
ideviewer reset --all --yes # no confirmation prompt
reset stops the daemon first, because removing the config from under a running daemon leaves it using the copy already in memory. It touches only local state — no host records are deleted from the portal. Removing a system-level config needs elevation (sudo, or an administrator prompt).
To remove a host from the portal as well, use Delete host on its page, or ideviewer deregistration before uninstalling.
The daemon is not picking up scan requests
The portal shows “Waiting for daemon to pick up request…” and nothing happens. Work down this list.
1. Is the daemon running at all?
ideviewer status
On Windows, older builds had two failure modes that both look like this:
- The daemon died when you closed the terminal. It inherited the launching console and received
CTRL_CLOSE_EVENTwhen that window closed. It is now started detached, with its own process group. ideviewer stopsilently failed — it sentSIGTERM, which Windows does not support — so it printed “already stopped”, deleted the PID file, and left the daemon running. Duplicate daemons could then accumulate, each holding whichever configuration it started with, and the portal heard from the wrong one.stopnow uses a Ctrl+Break to the process group and waits for the process to actually exit.
If it is not running:
ideviewer daemon --foreground # run in the terminal and watch the log
2. Does it survive a reboot?
| Platform | Mechanism | Check |
|---|---|---|
| Windows | Scheduled Task at logon | schtasks /Query /TN "IDEViewer Daemon" |
| macOS | LaunchAgent | launchctl print gui/$(id -u)/com.ideviewer.daemon |
| Linux | systemd unit | systemctl status ideviewer |
ideviewer register installs the appropriate one. If the Windows task is missing, re-run register, or create it by hand:
schtasks /Create /F /TN "IDEViewer Daemon" /SC ONLOGON /RL LIMITED ^
/TR "\"C:\Program Files\IDE Viewer\ideviewer.exe\" daemon"
3. Is it talking to the right portal?
ideviewer status prints the portal URL from the active config and probes it. A daemon pointed at a stale URL polls a portal that has no record of your request. See Reinstalling didn’t give me a clean slate.
4. Is it busy with another scan?
Only one scan runs at a time. A request that arrives mid-scan stays pending and is collected on a later poll (the daemon polls every 5 seconds). The log says:
Scan request #42 deferred — a scan is already running
5. Push the scan yourself
If the daemon will not collect the request, run the scan on the host and push it. This fulfils the pending request as well as submitting the report:
ideviewer scan --push
Found pending scan request #42 — fulfilling it from this CLI run
Report pushed to portal (source: cli)
Closed pending scan request #42
Does this overwrite results? No. Scan reports are append-only rows: a push adds one and re-derives the current-state tables, and retention only clears the raw payload of superseded reports. What a push can do is mislead, by making a hand-run scan look like routine telemetry — so every push is recorded with source=cli on the report, and the scan request’s log says it was fulfilled manually rather than by the daemon. Use --portal instead of --push to submit a report without touching any pending request.
A scan request is stuck at “Pending” and Stop does nothing
Fixed in this release, and worth knowing what you were seeing.
cancelled was missing from the database’s allowed scan-request statuses, so the Stop button’s write failed with a constraint violation and the request never left pending. Because a new scan is refused while one is active (HTTP 409), the Trigger Scan button then stayed disabled indefinitely.
Now:
- Stop cancels every active request for the host, not an arbitrary one. (It previously picked one row unordered while the page polled the newest, so they could disagree and the page kept showing “Pending”.)
- A request nobody claims within 10 minutes ages out to
timeouton its own, with the reason in the log. - A request whose daemon stops reporting progress for 30 minutes does the same.
- Either way, Trigger Scan becomes usable again.
If you are on an older portal build with rows already wedged, upgrading and running the migration is enough — the timeout sweeps them on the next page load:
cd portal && flask db upgrade
Webhooks are not firing
Webhook deliveries are triggered by events the portal ingests, so a daemon that never reports produces no events. Confirm the pipeline from the bottom up:
ideviewer statusshows the daemon running and the portal reachable.- A scan actually landed — the host page shows a recent report.
- The subscription is enabled and subscribed to the event you expect (Settings → Webhooks).
- Check Recent deliveries on the subscription for response codes; a non-2xx is recorded with the body.
- Force an event with
ideviewer scan --push, which produces a scan report and any resulting policy or extension-change events.
If deliveries show as queued but never sent, the portal’s job queue is not running. ./start.sh starts an RQ worker when Redis is available; without Redis the portal falls back to synchronous processing and logs that at startup.
Reading the logs
| Platform | Daemon log |
|---|---|
| Windows | %LOCALAPPDATA%\IDEViewer\logs\daemon.log |
| macOS | /tmp/ideviewer-daemon.log (LaunchAgent) or /var/log/ideviewer/daemon.log |
| Linux | /var/log/ideviewer/daemon.log, or journalctl -u ideviewer -f |
ideviewer status prints the exact path and when it was last written — a log that has not been touched for hours is itself the answer.
Portal logs go to the console for ./start.sh, and to docker-compose logs -f portal for --docker.
Getting help
Include the output of ideviewer status (it masks the customer key and host token) and the last 50 lines of the daemon log when opening an issue at github.com/securient/ideviewer-oss/issues.