Stop the flow store leaking git zombies, honour VITE_API_URL from .env

`FlowStore._git` now passes `-c gc.auto=0`, so `git commit` no longer forks a
background `gc --auto` that reparents onto PID 1 and stays there unreaped. With
auto-gc off nothing packs on its own, so `_commit` runs a foreground `git gc`
every 500 commits — the trigger a long-lived seeding session actually reaches.

`docker/compose.yml` takes the frontend's `VITE_API_URL` build arg from the
environment, keeping `https://api.${DOMAIN}` only as the fallback. `setup.sh`
already derives the scheme from `ENVIRONMENT`, so an `up --build` that does not
layer `compose.local.yml` stops shipping a bundle that calls `https://api.localhost`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01StpRc2C6au1WJ1EUU7fsfu
This commit is contained in:
2026-08-23 16:49:46 +02:00
co-authored by Claude Opus 5
parent 423968d9a0
commit cea0b26ed9
5 changed files with 46 additions and 24 deletions
+19 -1
View File
@@ -97,6 +97,13 @@ class FlowStore:
#: One warning per process when there is no git to commit with.
_warned_no_git = False
#: Commits between housekeeping runs. Auto-gc is off (see `_git`), so
#: nothing packs unless it is asked for; a commit counter is the trigger
#: because the process that needs it never restarts — a seeding session
#: commits thousands of times inside one long-lived container, which is how
#: a store reached 1898 commits, 5863 loose objects and no pack at all.
_GC_EVERY = 500
def __init__(self, root: Path) -> None:
self.root = root
self.root.mkdir(parents=True, exist_ok=True)
@@ -120,7 +127,13 @@ class FlowStore:
def _git(self, *args: str) -> subprocess.CompletedProcess[str]:
try:
return subprocess.run(
["git", "-C", str(self.root), *args],
# `gc.auto=0` on every invocation, because `git commit` otherwise
# forks a background `gc --auto` and returns without waiting for
# it. `subprocess.run` reaps the commit, not its grandchild, so
# the daemon is reparented onto PID 1 — the FastAPI process,
# which reaps nothing, and the zombies accumulate one per save.
# Packing is asked for explicitly in `_commit` instead.
["git", "-C", str(self.root), "-c", "gc.auto=0", *args],
capture_output=True,
text=True,
check=False,
@@ -158,6 +171,11 @@ class FlowStore:
message,
)
if result.returncode == 0 or "nothing to commit" in result.stdout:
if self.revision % self._GC_EVERY == 0:
# In the foreground, so this child is waited on rather than
# orphaned. Plain `gc` rather than `gc --auto`, which the flag
# above has just disabled.
self._git("gc", "--quiet")
return
if result.stderr == "git is not installed":
# Already said once, in `_git`. Repeating it per save is noise.