Skip to content

fix(docker): seed missing config defaults and surface pricing load failures - #845

Merged
SsuJojo merged 4 commits into
devfrom
fix/pr840-seed-recursion
Oct 8, 2026
Merged

SsuJojo merged 4 commits into
devfrom
fix/pr840-seed-recursion

Conversation

@SsuJojo

@SsuJojo SsuJojo commented Oct 8, 2026

Copy link
Copy Markdown
Collaborator

Supersedes the closed #840, whose source fork did not allow maintainer edits. Fixes the recursive seeding variable-scope defect found in review: each recursive call runs in a subshell so parent traversal paths remain intact. Adds a regression case with a nested directory sorted before a root-level file for both entrypoints.\n\nRelated: #837\n\nValidation:\n- Manual WSL /bin/sh regression: PASS for docker-entrypoint.sh and scripts/docker/lite-entrypoint.sh.\n- git diff --check: PASS.\n- npx vitest run tests/unit/ci/docker-entrypoint-seed.test.ts on Windows: 14 skipped because no usable sh executable was detected; WSL run of the test runner could not load the Linux Rollup optional dependency from the Windows node_modules. CI should execute the cases on Linux.

nieyuanhong and others added 4 commits October 8, 2026 20:17
…ilures

Both entrypoints only seeded the config volume when it was completely
empty, so an existing volume never received config files added by a newer
image. The reported symptom: model-pricing.yaml never reached /app/config,
token accounting worked, but every estimated cost stayed 0 (#837). Seeding
now runs on every start with `cp -rn`: missing files (including nested new
ones) are added, existing files - user edits and older defaults - are never
overwritten, the number of seeded files is logged, and a seeding failure
warns instead of aborting startup.

Pricing load failures were equally invisible: getCatalog() swallowed the
error and cached the empty catalog forever, and annotateUsageCost()
deliberately suppressed the ENOENT warning, so a deployment problem looked
like genuinely zero-cost usage. Failures now warn once with the resolved
file path and reason (deduplicated per path+reason with a 5 minute
cooldown), the empty catalog is only cached for 60 seconds before a retry,
and recovery is logged - dropping the missing file into the config volume
restores pricing without a restart.

Also narrows the .gitignore `logs/` rule to `/logs/`: the bare pattern also
matched source directories, so `git add src/logs/<file>` was refused and new
files under src/logs/ or tests/unit/logs/ would be silently ignored.

Refs #837
Verified in an alpine container: busybox `cp -rn` skips an existing
destination directory as a whole, so `cp -rn /defaults/. /app/config/`
copied nothing at all on the Lite image, and the glob form still missed
files nested inside an existing subdirectory (e.g. a new file in
prompts/). Replace it with an explicit recursive walk that copies only
missing files — same behavior under GNU and busybox cp — and keep
per-entry warnings so a partial failure stays visible without aborting
startup.

Refs #837
The image smoke test runs `node -e "...getProxyInfo().version..."` inside a
container without overriding the entrypoint, so it parses the container's
stdout for the version. An unconditional "0 missing file(s) seeded" line was
prepended to that output and made the version comparison fail.

Only report seeding when files were actually copied, and add a regression
test asserting the entrypoint prints nothing when the config volume already
holds every default file.

Refs #837
@SsuJojo
SsuJojo merged commit 8e1e9dd into dev Oct 8, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants