Skip to content

fix: ApplyCLIAutoStart respects ServerModeExternal (mirror NewFromConfigWithOptions)#3473

Merged
maphew merged 2 commits into
gastownhall:mainfrom
shaunc:fix/applyclirautostart-respects-external-mode
Apr 25, 2026
Merged

fix: ApplyCLIAutoStart respects ServerModeExternal (mirror NewFromConfigWithOptions)#3473
maphew merged 2 commits into
gastownhall:mainfrom
shaunc:fix/applyclirautostart-respects-external-mode

Conversation

@shaunc

@shaunc shaunc commented Apr 25, 2026

Copy link
Copy Markdown
Contributor

Summary

`ApplyCLIAutoStart` (in `internal/storage/dolt/open.go`) hardcoded
`ServerModeOwned` when calling `resolveAutoStart`, bypassing the
external-mode check that `resolveAutoStart`'s docstring lists as
priority 3 — the protection against the shadow database bug:
when a repo has `metadata.json dolt_server_port` set (External
mode) and the configured server is transiently unreachable, bd
was free to spawn a fallback embedded server from `.beads/dolt/`.

The companion path `NewFromConfigWithOptions` correctly resolves
the actual server mode (open.go:104) and passes it to
`resolveAutoStart`, so external-mode suppression worked there.
The `cmd/bd/main.go:889` path that routes through
`ApplyCLIAutoStart` silently lost the protection.

Real-world failure mode

Hit at factfiber.ai 2026-04-24 during a service cutover that
briefly took the team's central Dolt server offline. A bd
write hook fired in an external-mode repo, hit a transient
connect-failure to the configured server, and spawned a shadow
`dolt sql-server` on the same loopback port. The orphan
ran for 18 hours, intercepting bd commands from other shells
and surfacing "database not found" errors against an empty
fallback chunkstore. Investigation in
this team's notes
traced it to `ApplyCLIAutoStart`'s hardcoded `ServerModeOwned`.

Patch

```diff
func ApplyCLIAutoStart(beadsDir string, cfg *Config) {
autoStartCfg := config.GetString("dolt.auto-start")
if autoStartCfg == "" {
autoStartCfg = config.GetStringFromDir(beadsDir, "dolt.auto-start")
}

  • // Pass ServerModeOwned to avoid external-mode suppression — this path
  • // intentionally behaves like a standalone owned-server setup.
  • cfg.AutoStart = resolveAutoStart(true, autoStartCfg, ServerModeOwned)
  • mode := doltserver.ResolveServerMode(beadsDir)
  • cfg.AutoStart = resolveAutoStart(true, autoStartCfg, mode)
    }
    ```

Plus an updated doc comment explaining why the prior rationale
("so doctor and other CLI helper paths behave the same way as
cmd/bd/main.go on cold repo-local standalone setups") is no
longer load-bearing once respected: a cold standalone post-bd-init
already has its server running, so no fallback auto-start is
needed; if the user explicitly stops the server, External mode's
"you manage the lifecycle" semantics asks the user to run
`bd dolt start`.

Test plan

  • New `TestApplyCLIAutoStart_RespectsExternalMode` regression
    in `internal/storage/dolt/open_test.go` — metadata.json with
    `dolt_server_port` set + no config.yaml `dolt.auto-start` →
    `cfg.AutoStart` must be false. Pre-fix this returned true.
  • `go test ./internal/storage/dolt/ ./internal/configfile/ ./internal/config/`
    passes (with `BEADS_DOLT_SERVER_HOST` cleared from env —
    pre-existing test isolation requirement).
  • Existing `TestResolveAutoStart` cases unchanged.

Notes

…figWithOptions)

ApplyCLIAutoStart hardcoded ServerModeOwned when calling
resolveAutoStart, bypassing the external-mode check (priority 3
in resolveAutoStart's docstring) that prevents the "shadow
database bug": when a repo has metadata.json dolt_server_port
set (External mode) and the configured server is transiently
unreachable, bd was free to spawn a fallback embedded server
from .beads/dolt/.

The companion path NewFromConfigWithOptions correctly resolves
the actual server mode (open.go:104) and passes it to
resolveAutoStart, so external-mode suppression worked there.
The cmd/bd/main.go:889 path that routes through ApplyCLIAutoStart
silently lost the protection.

Real-world failure mode (factfiber.ai 2026-04-24): during a
silk/Dolt service cutover, a bd write hook in an external-mode
repo fell into ApplyCLIAutoStart's path, hit a transient connect
failure to the configured server, and spawned a shadow Dolt
sql-server on the same loopback port. The orphan ran for 18
hours intercepting bd commands from other shells and surfacing
"database not found" errors.

Fix: have ApplyCLIAutoStart call doltserver.ResolveServerMode
and pass the actual mode, mirroring the NewFromConfigWithOptions
shape.

The function's prior comment ("intentionally ignores metadata.json
explicit-port suppression so doctor and other CLI helper paths
behave the same way as cmd/bd/main.go on cold repo-local
standalone setups") is no longer load-bearing once respected:
for a cold standalone setup post-bd-init, the server is already
running, so no fallback auto-start is needed; if the user
explicitly stops the server, External mode's "you manage the
lifecycle" semantics correctly asks the user to run
'bd dolt start'.

Add TestApplyCLIAutoStart_RespectsExternalMode regression test:
metadata.json with dolt_server_port set + no config.yaml
dolt.auto-start → cfg.AutoStart must be false.
@codecov-commenter

codecov-commenter commented Apr 25, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ All tests successful. No failed tests found.

📢 Thoughts on this report? Let us know!

@maphew maphew left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the contributor fix and pushed a small maintainer repair to align the stale doctor autostart test with the new external-mode behavior. Preserved the regression test and validated locally with targeted package tests plus make test; CI is green.

@maphew maphew merged commit cb3ef71 into gastownhall:main Apr 25, 2026
39 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.

4 participants