Troubleshooting

Real failure modes, the actual text Goalfeed produces, and the next concrete step — not a generic "check your configuration."

First move, always

Every event attempt (success or failure) is written to the JSONL app log and queryable at /api/logs whether or not Home Assistant is reachable — that log is where every error below actually shows up. Start there:

bash
curl -s "http://localhost:8080/api/logs?limit=5" | python3 -m json.tool
# or, without --web, read the file directly:
tail -n 5 app.log.jsonl

"home assistant url is empty"

Where you'll see it: an app log entry with "target": "ha:event:goal", "success": false, and "error": "refusing to send Home Assistant request: home assistant url is empty" (this is real, captured output — not a paraphrase). No exception, no crash — Goalfeed keeps polling and logging, it just can't reach Home Assistant yet.

Cause: neither home_assistant.url/ home_assistant.access_token in config.yaml (or the matching GOALFEED_HOME_ASSISTANT_* env vars) nor the Supervisor add-on env vars (SUPERVISOR_API/SUPERVISOR_TOKEN) are set.

Next step: set both keys in config.yaml (see the configuration reference) if you're not running as the Home Assistant add-on, or confirm the add-on's Supervisor integration is enabled if you are. Then check /api/homeassistant/status.

"does not look like a private/local address"

Where you'll see it: the same app log target, with an error like refusing to send Home Assistant request: home assistant url "http://ha.example.com" does not look like a private/local address; set home_assistant.allow_remote_url: true if you intentionally run a remote Home Assistant instance (real error text from utils/hurl.go).

Cause: home_assistant.url resolves to a public hostname or routable IP. Goalfeed refuses this by default so a poisoned or hand-edited config can't send your long-lived access token to an attacker-controlled host — see Security for the full reasoning.

Next step: if this is genuinely a remote Home Assistant instance you control, set home_assistant.allow_remote_url: true. If it isn't — if you didn't expect a public hostname here at all — treat this as the check working as intended and fix the URL instead.

"Failed to send event to Home Assistant" / a non-2xx status in the log

Where you'll see it: an app log entry with "success": false and an "error" field containing an HTTP status like 401 Unauthorized or 404 Not Found, rather than the URL-validation message above (so the URL passed validation, but the request itself failed).

Cause: usually an expired or wrong long-lived access token (401), or a URL that's reachable but wrong (404 if it doesn't point at a real Home Assistant instance's REST API).

Next step: generate a fresh long-lived access token from your Home Assistant profile page, update home_assistant.access_token, and restart Goalfeed. If you're on the add-on, this shouldn't happen at all — the Supervisor token doesn't expire the way a manually-created one can; check /api/homeassistant/status instead.

The web UI shows a bare JSON status blob instead of the scoreboard

Where you'll see it: opening http://<host>:8080/ returns {"api":"/api","message":"Goalfeed API Server","status":"running","ws":"/ws","note":"Frontend not available. Install Node.js and npm to enable the web interface."} (real, captured output) instead of the React scoreboard.

Cause: the web/frontend/build directory doesn't exist next to the binary. This happens if you built with make backend instead of make build, or ran a Go binary built from a bare go build without the frontend step (the plain Dockerfile always produces this — see Install). The REST API and WebSocket feed still work in this mode; only the bundled UI is missing.

Next step: use a release tarball from GitHub Releases (bundles the frontend), or run make build / make frontend from a source checkout with Node 20+ installed.

Startup logs "Frontend build failed: package.json not found... Install Node.js" even though the web UI works fine

Where you'll see it: on a release tarball, in --web mode, on startup (real, captured output against a v1.0.39 archive):

console
2026/08/21 08:24:19 Frontend build failed: package.json not found in ./web/frontend
2026/08/21 08:24:19 Serving API-only mode. Install Node.js and npm to enable the web interface.

— while http://<host>:8080/ in that same run serves the actual React scoreboard, not the JSON status blob further up this page. The two things contradict each other: the log says the frontend failed and to install Node.js; the browser shows the frontend working.

Cause: a real bug in v1.0.39 and earlier. Startup always tried to build the frontend from web/frontend/package.json, which a release archive never ships (it ships the already-built web/frontend/build directory instead, not the source tree) — so that step always failed and always logged the Node.js message, on every release-binary startup, regardless of whether the UI was actually about to work. Static file serving is a separate code path that checks for web/frontend/build directly, found it, and served it correctly anyway — which is why the UI loads even though the log line says it shouldn't. Ignore this message on a release binary if the web UI actually loads in your browser; it's log noise, not a real failure.

Next step: this is fixed as of the release after v1.0.39 — the startup check now looks for the prebuilt web/frontend/build/index.html first and skips the Node.js-only build step entirely when it's already there, so the message no longer prints on a release binary. If you're still on v1.0.39 or earlier and want the log to stop lying to you, upgrade (see Upgrade and uninstall); there is nothing to fix on your end, and installing Node.js changes nothing for a release binary.

"I set --nfl WPG and got a flag error"

Cause: --nfl isn't a real flag. The only registered CLI flags are --nhl, --mlb, and --cfl. The NFL team list is YAML/env-only (watch.nfl, GOALFEED_WATCH_NFL, etc.).

Next step: set those via config.yaml or a GOALFEED_WATCH_* environment variable instead — see the full table in the configuration reference.

Config changes don't seem to take effect, and there's no error at all

Cause: Goalfeed looks for ./config.yaml in the current working directory only — there is no --config flag to point it elsewhere. If it isn't found, the config read fails silently (the error is discarded internally) and every key falls back to its zero value: no watched teams, no Home Assistant URL, headless mode.

As of the release after v1.0.39, this prints one line and tells you so. If no config.yaml was found and no team was set by any other means (flag or GOALFEED_* env var), startup now writes a single actionable line to stderr before anything else logs, naming the working directory it looked in and what to do next:

console
No config.yaml found in /home/pi/goalfeed, and no teams set via flags or GOALFEED_* env vars,
so Goalfeed has nothing to watch. Create a config.yaml next to the binary (see
config.example.yaml in the release archive), or pass --nhl WPG to try it right now.
Full reference: https://goalfeed.ca/docs/configuration/

On v1.0.39 and earlier this notice doesn't exist yet, so a missing config.yaml there really does look like an idle process with nothing in the log to explain why — that's the case the rest of this entry is for.

Next step: confirm you're launching Goalfeed from the same directory as config.yaml (systemd/Supervisor users: check the service's WorkingDirectory). If in doubt, set at least one option via CLI flag or GOALFEED_* env var instead — those don't depend on the working directory.

POST /api/refresh doesn't do anything

Cause: it's a stub left over from a refactor. It returns HTTP 200 and logs "Refresh active games - placeholder implementation", but doesn't actually refresh anything.

Next step: don't rely on it. Active games refresh automatically every minute (checkLeaguesForActiveGames); if you need it sooner, restart the process.

Home Assistant config changes I made through the web UI disappeared on restart

Cause: by default, POST /api/homeassistant/config only updates the running process's in-memory config for the current session — it does not write config.yaml to disk unless you've opted in. The response message says so explicitly: "Configuration updated for this session only (not persisted to disk). Set web.allow_config_writes: true in config.yaml to allow this API to write config.yaml."

Next step: edit config.yaml directly for changes that should survive a restart, or set web.allow_config_writes: true if you specifically want the runtime API to persist config to disk — see Security for why this defaults to off.

Still stuck?

Open an issue with the exact log line and your config.yaml (redact the access token) at github.com/goalfeed/goalfeed/issues — that's the only supported contact channel.