Files
remote-mcp-manager/LESSONS.md
Dasemu 4cf857fd2d fix: pin NIXPACKS_NODE_VERSION from the repo's .nvmrc/engines.node
Nixpacks silently falls back to its own default Node version when a
repo doesn't match what it expects, instead of failing the build —
confirmed in practice as a green build that crashed at runtime on a
missing global. detectNodeVersion() reads the repo's own .nvmrc or
package.json engines.node and passes it through as
NIXPACKS_NODE_VERSION so the build actually uses what the repo
declares.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-08 03:12:58 +02:00

3.4 KiB

Lessons

Recurring mistakes and gotchas found while building and operating this project, and how to avoid or work around them.

Nixpacks silently picks its own default runtime version

Nixpacks does not fail when a repo's engines/version requirement isn't met — it silently falls back to its own default (e.g. picked Node 18 for a repo requiring Node >=20 in Social-MCP). The build goes green, then the container crashes at runtime (crypto is not defined, missing globals, etc.).

Avoid it: don't trust a green build as proof the runtime is correct. The manager now detects a Node version hint from the repo itself (.nvmrc, else package.json engines.node) and passes it to Nixpacks as NIXPACKS_NODE_VERSION (apps/server/src/build/nixpacks.ts, detectNodeVersion), so the build uses the version the repo actually declares instead of Nixpacks' own default. Repos with no version hint at all still get Nixpacks' default, unchanged.

Nixpacks needs a dependency manifest, and fails cleanly without one

No requirements.txt/pyproject.toml (Python) or package.json (Node) in the installed repo → Nixpacks fails with "Nixpacks was unable to generate a build plan" (exit 1). This is expected and not something the manager works around generically — several of the user's own Python MCPs lack a manifest entirely.

Avoid it: always surface the full build log in the UI so this failure is immediately diagnosable; don't try to add generic manifest-inference logic.

Piped exit codes mask the real command result

Piping a subprocess's output through another command (e.g. | tee) loses the original exit code in $?. This hid a real nixpacks build failure during manual testing.

Avoid it: redirect output straight to a file and check $? immediately after the command, don't pipe when the exit code matters.

Rootless Podman isolates the host network namespace from the container bridge

The host cannot reach a container's bridge-network IP directly under rootless Podman (netavark backend, "isolate": "true"). Only another container on the same network can reach it.

Avoid it: for local dev, run the manager itself as a container attached to mcp-net — this also matches the production topology, where the manager runs as a container too.

SELinux can deny access to a bind-mounted socket even with matching UID/GID

Got an EACCES on the Podman socket despite --userns=keep-id lining up UID/GID. Root cause was an SELinux AVC denial in enforcing mode, not a permissions issue.

Avoid it: when socket access fails despite correct ownership, check audit.log/ausearch for AVC denials before chasing permissions further. --security-opt label=disable confirms the diagnosis but is not a production fix — use a proper SELinux policy/context instead.

Podman images must be referenced fully-qualified

A short image name wasn't enough to remove an image via the Docker-compatible socket under Podman — needed the fully-qualified localhost/... name.

Avoid it: always use the fully-qualified name dockerode/the Docker API returns, don't assume short names round-trip.

npm workspaces hoist node_modules to the repo root

Mounting only apps/server into a test container produced node_modules not found — the hoisted root node_modules wasn't mounted.

Avoid it: mount the whole repo root when testing a workspace package, not just its subdirectory.