gastownhall/beads · error

ExternalDoltConfig: TLSCert set without TLSKey

Error message

ExternalDoltConfig: TLSCert set without TLSKey

What it means

When request.WaitsFor is present, SpawnerID must be non-empty and Gate, if set, must be exactly "all-children" (types.WaitsForAllChildren) or "any-children" (types.WaitsForAnyChildren). Anything else is refused with this fixed message wrapped as ErrValidation.

Source

Thrown at internal/configfile/external_dolt_config.go:65

	case !hasSocket && !hasHost && !hasPort:
		return errors.New("ExternalDoltConfig: must set Socket or (Host, Port)")
	case hasHost && !hasPort:
		return errors.New("ExternalDoltConfig: Host requires Port")
	case !hasHost && hasPort:
		return errors.New("ExternalDoltConfig: Port requires Host")
	}

	if hasHost && (c.Port < 1 || c.Port > 65535) {
		return fmt.Errorf("ExternalDoltConfig: Port %d out of range [1, 65535]", c.Port)
	}

	if hasSocket && !filepath.IsAbs(c.Socket) {
		return fmt.Errorf("ExternalDoltConfig: Socket %q is not absolute", c.Socket)
	}

	switch {
	case c.TLSCert != "" && c.TLSKey == "":
		return errors.New("ExternalDoltConfig: TLSCert set without TLSKey")
	case c.TLSCert == "" && c.TLSKey != "":
		return errors.New("ExternalDoltConfig: TLSKey set without TLSCert")
	}

	if c.TLSCert != "" && !filepath.IsAbs(c.TLSCert) {
		return fmt.Errorf("ExternalDoltConfig: TLSCert %q is not absolute", c.TLSCert)
	}
	if c.TLSKey != "" && !filepath.IsAbs(c.TLSKey) {
		return fmt.Errorf("ExternalDoltConfig: TLSKey %q is not absolute", c.TLSKey)
	}
	if c.TLSCACert != "" && !filepath.IsAbs(c.TLSCACert) {
		return fmt.Errorf("ExternalDoltConfig: TLSCACert %q is not absolute", c.TLSCACert)
	}

	if !c.TLSRequired {
		switch {
		case c.TLSCACert != "":
			return errors.New("ExternalDoltConfig: TLSCACert set without TLSRequired")

View on GitHub (pinned to 71377f2769)

Solutions

  1. Set SpawnerID to the spawner issue's ID and Gate to string(types.WaitsForAllChildren) or string(types.WaitsForAnyChildren).
  2. Leave Gate empty to get the default (all-children is applied in PreparePublicCreateRequest).
  3. Validate the gate against the constants rather than raw strings.

Example fix

// before
WaitsFor: &publicops.WaitsForInput{SpawnerID: "mol-1", Gate: "all_children"} // invalid gate
// after
WaitsFor: &publicops.WaitsForInput{SpawnerID: "mol-1", Gate: string(types.WaitsForAllChildren)}
Defensive patterns

Strategy: validation

Validate before calling

if req.WaitsFor != nil {
    if req.WaitsFor.SpawnerID == "" ||
       (req.WaitsFor.Gate != "" && req.WaitsFor.Gate != string(types.WaitsForAllChildren) && req.WaitsFor.Gate != string(types.WaitsForAnyChildren)) {
        return errors.New("waits-for spawner and gate are invalid")
    }
}

Type guard

func waitsForValid(w *publicops.WaitsForInput) bool {
    if w == nil { return true }
    return w.SpawnerID != "" && (w.Gate == "" || w.Gate == string(types.WaitsForAllChildren) || w.Gate == string(types.WaitsForAnyChildren))
}

Try / catch

if err := store.ExecuteCreate(ctx, req); err != nil {
    if errors.Is(err, storage.ErrValidation) && strings.Contains(err.Error(), "waits-for spawner and gate are invalid") { /* fix gate/spawner */ }
    return err
}

Prevention

When it happens

Trigger: ExecuteCreate/ValidatePublicCreateRequest where WaitsFor.SpawnerID == "" or WaitsFor.Gate is a non-empty string other than the two allowed gate constants; check at public_create.go:187.

Common situations: Typo in the gate string ("all_children", "any", "ALL"), case mismatch, leaving SpawnerID unset while only setting Gate, or copying gate names from older API versions.

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/14ce28f72275aed9. Report an issue: GitHub.