Problem
The in-app scheduler hardcodes 127.0.0.1:3000 for loopback POSTs, but the actual server port may differ (via PORT env or container runtime). All periodic jobs (sync, groomer, pr-followup, pruning) silently fail if the port doesn't match.
Evidence
src/lib/scheduler.ts — const port = env.PORT?.trim() || "3000" but if PORT=8080, scheduler POSTs to 127.0.0.1:3000
Dockerfile — ENV HOSTNAME=0.0.0.0 ensures binding but doesn't affect scheduling URL
Acceptance
Use 127.0.0.1:${port} consistently with port resolved from PORT env, or detect the actual listening port at startup. Add a startup health check that verifies the scheduler can reach its own endpoints.
Decomposed from #648 (Weekly tech debt audit: misospace/dispatch - 2026-07-22).
Problem
The in-app scheduler hardcodes
127.0.0.1:3000for loopback POSTs, but the actual server port may differ (viaPORTenv or container runtime). All periodic jobs (sync, groomer, pr-followup, pruning) silently fail if the port doesn't match.Evidence
src/lib/scheduler.ts—const port = env.PORT?.trim() || "3000"but ifPORT=8080, scheduler POSTs to127.0.0.1:3000Dockerfile—ENV HOSTNAME=0.0.0.0ensures binding but doesn't affect scheduling URLAcceptance
Use
127.0.0.1:${port}consistently withportresolved fromPORTenv, or detect the actual listening port at startup. Add a startup health check that verifies the scheduler can reach its own endpoints.Decomposed from #648 (Weekly tech debt audit: misospace/dispatch - 2026-07-22).