Troubleshooting
Real failure modes, the actual text Goalfeed produces, and the next concrete step — not a generic "check your configuration."
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:
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):
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:
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.