gastownhall/beads · error

ExternalDoltConfig: TLSKey set without TLSCert

Error message

ExternalDoltConfig: TLSKey set without TLSCert

What it means

An edge is rejected when its source and target resolve to the same endpoint — i.e. the new issue would depend on itself (or, when Issue.ID matches a referenced ID, the edge collapses onto the new issue). This wraps domain.ErrSelfDependency with the endpoint ID and storage.ErrValidation. Self-dependencies are logically meaningless and would corrupt dependency graphs/cycle detection.

Source

Thrown at internal/configfile/external_dolt_config.go:67

	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")
		case c.TLSCert != "" || c.TLSKey != "":
			return errors.New("ExternalDoltConfig: TLSCert/TLSKey set without TLSRequired")

View on GitHub (pinned to 71377f2769)

Solutions

  1. Remove the dependency entry whose TargetID equals the new issue's ID.
  2. Skip such edges when building dependency lists programmatically.
  3. Check before submitting: reject any dep where TargetID == request.Issue.ID (or ParentID == Issue.ID).

Example fix

// before
Issue.ID = "bd-1"; req.Dependencies = []publicops.DependencyInput{{TargetID: "bd-1", Type: types.DepBlocks}}
// after
req.Dependencies = slices.DeleteFunc(req.Dependencies, func(d publicops.DependencyInput) bool { return d.TargetID == issue.ID })
Defensive patterns

Strategy: validation

Validate before calling

if req.Issue != nil && req.Issue.ID != "" {
    if req.ParentID == req.Issue.ID { return errors.New("self parent") }
    for _, d := range req.Dependencies {
        if d.TargetID == req.Issue.ID { return errors.New("self dependency: " + d.TargetID) }
    }
}

Try / catch

if err := store.ExecuteCreate(ctx, req); err != nil {
    if errors.Is(err, storage.ErrValidation) && errors.Is(err, domain.ErrSelfDependency) { /* drop the self edge */ }
    return err
}

Prevention

When it happens

Trigger: ExecuteCreate/ValidatePublicCreateRequest where a dependency target equals the new issue's ID (endpointFor maps it to the new-issue endpoint), or request.ParentID equals Issue.ID; check at public_create.go:198.

Common situations: Auto-generating dependency lists that accidentally include the issue being created; reverse dependencies where the new issue's own ID was used as TargetID; copy-paste of an ID template with the same value in both fields.

Related errors


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