juanfont/headscale · error

loading config: %w

Error message

loading config: %w

What it means

Thrown by bypassDatabase() in the policy CLI commands when types.LoadServerConfig() fails. Bypass mode (--bypass-server-and-access-database-directly) opens the database directly, which first requires loading the server configuration file from disk. The wrapped error names the config file and the parse failure.

Source

Thrown at cmd/headscale/cli/policy.go:29

	"github.com/juanfont/headscale/hscontrol/db"
	"github.com/juanfont/headscale/hscontrol/policy"
	"github.com/juanfont/headscale/hscontrol/types"
	"github.com/spf13/cobra"
	"tailscale.com/types/views"
)

const (
	bypassFlag = "bypass-server-and-access-database-directly" //nolint:gosec // not a credential
)

var errAborted = errors.New("command aborted by user")

// bypassDatabase opens the database directly, bypassing the running server.
// The caller must close the returned handle.
func bypassDatabase() (*db.HSDatabase, error) {
	cfg, err := types.LoadServerConfig()
	if err != nil {
		return nil, fmt.Errorf("loading config: %w", err)
	}

	d, err := db.NewHeadscaleDatabase(cfg)
	if err != nil {
		return nil, fmt.Errorf("opening database: %w", err)
	}

	return d, nil
}

// openBypassDB confirms the destructive bypass action and opens the database
// directly. The caller is responsible for closing the returned handle.
func openBypassDB(cmd *cobra.Command) (*db.HSDatabase, error) {
	if !confirmAction(cmd, "DO NOT run this command if an instance of headscale is running, are you sure headscale is not running?") {
		return nil, errAborted
	}

	return bypassDatabase()

View on GitHub (pinned to 565fd254d0)

Solutions

  1. Pass the config explicitly: `headscale --config /etc/headscale/config.yaml policy ...` or set HEADSCALE_CONFIG_PATH.
  2. Read the wrapped error — it names the exact file and YAML problem (line/column).
  3. Validate the file parses: `headscale config view` (non-bypass path uses the same loader).
  4. If keys were renamed in an upgrade, re-check the config reference in docs/ for your version.

Example fix

# before
headscale policy get --bypass-server-and-access-database-directly

# after
headscale --config /etc/headscale/config.yaml policy get --bypass-server-and-access-database-directly
Defensive patterns

Strategy: validation

Validate before calling

// before running a bypass command, assert the config exists and parses
func checkConfig(path string) error {
    fi, err := os.Stat(path)
    if err != nil {
        return fmt.Errorf("config missing: %w", err)
    }
    if fi.IsDir() {
        return fmt.Errorf("config path is a directory: %s", path)
    }
    return nil
}

Try / catch

if _, err := os.Stat(cfgPath); err != nil {
    log.Fatalf("config check failed: %v — pass --config explicitly", err)
}

Prevention

When it happens

Trigger: Running `headscale policy get/set/check --bypass-server-and-access-database-directly` when the configuration file does not exist at the expected path, is unreadable, or contains invalid YAML. Also triggered by config keys with invalid values (bad duration, unknown enum).

Common situations: Running the CLI from a directory other than where headscale config lives without setting the config path flag/env; config migrated between headscale versions with renamed keys; permissions on /etc/headscale/config.yaml; leftover BOM or tabs breaking YAML parsing.

Related errors


AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15). Data as JSON: /api/errors/92e2fd13e4d5850f. Report an issue: GitHub.