[TEST] fix/boot-options — dynamic port handling on boot + docker controls on shutdown#61
Merged
Conversation
Replace the generic markdown bug template with YAML Issue Forms tailored to Bytebell, plus a feature request form and a chooser config.yml. - bug_report.yml: structured form with required fields (summary, repro, expected, version) and optional fields for component, LLM provider, OS, Bun version, logs, and context. Dropdown for affected component includes "Not sure" so contributors aren't gated on architectural knowledge. - feature_request.yml: slimmed to problem / proposal / alternatives / context so OSS contributors can file ideas without first reading CLAUDE.md or understanding the tier model. - config.yml: disables blank issues, routes security reports to SECURITY.md (team@bytebell.ai) instead of GHSA, links Discussions for questions, and surfaces contributing.md for newcomers.
Update issue templates
There was a problem hiding this comment.
Pull request overview
This PR enhances the CLI’s local dev ergonomics by making bytebell boot resilient to Docker port conflicts (interactive diagnosis + resolution with retries) and extending bytebell shutdown to optionally tear down Docker infra (prompted in TTYs, deterministic flags for scripts).
Changes:
- Added interactive Docker port-conflict diagnosis + resolution flow during
bytebell boot(reuse/kill/change/cancel) and threaded configurable host ports into compose via.env. - Added
bytebell shutdowninfra management via--with-docker/--keep-dockerand a TTY Ink prompt. - Refactored Docker infra management utilities to support per-service startup and improved error typing (port-conflict error).
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/cli/src/BootCommand.ts | Adds retry loop + interactive port-conflict handling and service skipping for boot. |
| packages/cli/src/dockerInfra.ts | Adds down(), supports passing port values + selective service startup, introduces DockerPortConflictError. |
| packages/cli/src/dockerPortDiagnostics.ts | Adds compose-stderr port parsing + diagnostics for container/process occupying a port. |
| packages/cli/src/infraPorts.ts | Derives/writes infra host ports from configured URIs and generates compose .env body. |
| packages/cli/src/portConflictPrompt.ts | Wires diagnostics into the interactive port-conflict selector prompt. |
| packages/cli/src/PortConflictSelector.tsx | Ink UI for resolving port conflicts (reuse/kill/change/cancel + port input). |
| packages/cli/src/ShutdownCommand.ts | Adds flags + prompting to optionally stop Docker infra during shutdown. |
| packages/cli/src/shutdownPrompts.ts | Ink render helper for the shutdown “stop Docker?” prompt. |
| packages/cli/src/StopInfraPrompt.tsx | Ink UI for shutdown infra stop confirmation. |
| packages/cli/README.md | Documents new boot conflict-recovery behavior and shutdown Docker controls. |
| infra/docker/docker-compose.yml | Switches hardcoded host ports to env-substituted ${…:-default} ports. |
Comments suppressed due to low confidence (1)
packages/cli/src/ShutdownCommand.ts:41
- When the PID file is missing/stale (
pid === null), shutdown returns early and never evaluates--with-docker/--keep-docker(or the interactive prompt). This meansbytebell shutdown --with-dockerwon’t stop Docker infra if the server is already down, which seems to contradict the new flag semantics. Consider still runningdecideStopDocker()/stopDocker()in this branch (or at least honoring--with-docker).
const pidFile = path.join(getBytebellHome(), "pid");
const pid = await readPid(pidFile);
if (pid === null) {
success("server is not running.");
process.stdout.write(dockerHint());
return;
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| stderr += chunk.toString("utf8"); | ||
| }); | ||
| child.on("error", (cause: Error) => reject(cause)); | ||
| child.on("exit", () => resolve({ stdout, stderr })); |
| if (skip.size === 0) { | ||
| return ["mongo", "neo4j", "redis"]; | ||
| } | ||
| return (["mongo", "neo4j", "redis"] as const).filter((s) => !skip.has(s)); |
Comment on lines
+54
to
+57
| case "neo4j-bolt": | ||
| case "neo4j-http": | ||
| setConfigValue(Config.Neo4jUri, replacePort(readString(Config.Neo4jUri), boltPortForService(service, newPort))); | ||
| return; |
Comment on lines
+150
to
+155
| if (props.canKill) { | ||
| return { | ||
| action: "kill", | ||
| label: `Stop the conflicting container and reuse port ${props.port}`, | ||
| hint: `docker rm -f ${props.occupantLabel}`, | ||
| }; |
Comment on lines
219
to
+223
| resolve({ stdout, stderr }); | ||
| return; | ||
| } | ||
| const port = parsePortFromComposeError(stderr); | ||
| if (port !== null) { |
nitesh32
approved these changes
May 24, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed
bytebell bootnow recovers from Docker port conflicts interactively instead of failing outright:bind: address already in useon mongo/neo4j/redis and identifies the offending port.dockerPortDiagnostics.ts.PortConflictSelector.tsx) to reuse the existing service, kill the conflicting container, change bytebell's port, or cancel — with up to 4 retry rounds.~/.bytebell/via newinfraPorts.ts, and threads them into compose asMONGO_HOST_PORT/NEO4J_HTTP_HOST_PORT/NEO4J_BOLT_HOST_PORT/REDIS_HOST_PORTenv vars (compose file defaults preserved).docker compose upand continues.bytebell shutdownnow manages Docker infra instead of always leaving it running:--with-dockerand--keep-dockerflags (mutually exclusive) for non-interactive use.StopInfraPrompt.tsxwhether to also tear down Docker.BootCommand.tsinto smaller functions (bringInfraUp,handlePortConflict,startServer) so the conflict-recovery loop is readable.DockerPortConflictError+down()todockerInfra.ts;up()now acceptsportsandservicesToStart.packages/cli/README.mdto document the new behaviour.Why
Two recurring boot-time pain points on developer machines:
Bind for 127.0.0.1:6379 failed: port is already allocatedand had no remediation path other than reading the docker error and manually editing compose. Boot now diagnoses and offers concrete actions.bytebell shutdownonly stopped the server, leaving mongo/neo4j/redis running. Users had to rememberdocker compose -f …to fully tear down. Nowshutdowncan do both, with explicit flags for CI/scripts.Per project rules: all state stays under
~/.bytebell/(no.env), all outbound calls unchanged, single-tenantorgId=localinvariants untouched.How to test
Port conflict recovery on boot
Run bytebell boot.
Expect: spinner fails, then the PortConflictSelector appears showing that port 6379 is held by container rogue-redis. Options: Reuse, Kill container, Change port, Cancel.
Pick Kill container → boot retries, container is removed, bytebell's redis comes up on 6379.
Re-run with rogue-redis back up and pick Change port → enter e.g. 6390 → boot continues, and ~/.bytebell/infra-ports.json (or wherever infraPorts.ts persists) now records the override. Subsequent boots reuse 6390.
Re-run and pick Reuse → boot succeeds and logs reusing existing service on port 6379 for redis (not managed by bytebell).
Cleanup: docker rm -f rogue-redis.
Non-docker port holder
python3 -m http.server 27017 (or any process binding the mongo port).
bytebell boot → conflict prompt should detect there is no container holding the port and Kill option should be disabled / explain you must stop the process yourself.
Shutdown flags
bytebell boot (let infra start).
bytebell shutdown --with-docker → server stops, then docker compose down runs; docker ps shows no bytebell containers.
bytebell boot again.
bytebell shutdown --keep-docker → server stops, infra still running (docker ps shows mongo/neo4j/redis).
bytebell shutdown interactively (TTY) → prompts "Stop Docker infra too?"; both Yes and No paths exit cleanly.
bytebell shutdown --with-docker --keep-docker → exits 1 with the mutually-exclusive error.
Regression sanity
Boot with no conflicts behaves exactly as before (no extra prompts, same success output).
bytebell shutdown over a pipe / in a script (non-TTY) leaves Docker running by default — no breaking change for existing automation.