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.

"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. NFL, IIHF, and Olympic Men's/Women's Hockey team lists are 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. Nothing crashes; you just get an idle process.

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.

IIHF games show up in the API but no goal ever fires

Cause: this is expected, not a bug. IIHF has a full client and service and answers /api/games/history, /api/upcoming, and /api/teams, but it is never registered in the live polling loop, so no Home Assistant event fires for it. See the leagues reference for the full tier breakdown.

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.