glanceapp/glance · error

reading %s: %w

Error message

reading %s: %w

What it means

An I/O failure while reading the main config file (or an included file, since recursiveParseYAMLIncludes is invoked per include) during include resolution. The path is included in the message and the underlying os.ReadFile error is wrapped with %w, exposing the OS-level cause (ENOENT, permission, etc.).

Source

Thrown at internal/glance/config.go:253

func formatWidgetInitError(err error, w widget) error {
	return fmt.Errorf("%s widget: %v", w.GetType(), err)
}

var configIncludePattern = regexp.MustCompile(`(?m)^([ \t]*)(?:-[ \t]*)?(?:!|\$)include:[ \t]*(.+)$`)

func parseYAMLIncludes(mainFilePath string) ([]byte, map[string]struct{}, error) {
	return recursiveParseYAMLIncludes(mainFilePath, nil, 0)
}

func recursiveParseYAMLIncludes(mainFilePath string, includes map[string]struct{}, depth int) ([]byte, map[string]struct{}, error) {
	if depth > CONFIG_INCLUDE_RECURSION_DEPTH_LIMIT {
		return nil, nil, fmt.Errorf("recursion depth limit of %d reached", CONFIG_INCLUDE_RECURSION_DEPTH_LIMIT)
	}

	mainFileContents, err := os.ReadFile(mainFilePath)
	if err != nil {
		return nil, nil, fmt.Errorf("reading %s: %w", mainFilePath, err)
	}

	mainFileAbsPath, err := filepath.Abs(mainFilePath)
	if err != nil {
		return nil, nil, fmt.Errorf("getting absolute path of %s: %w", mainFilePath, err)
	}
	mainFileDir := filepath.Dir(mainFileAbsPath)

	if includes == nil {
		includes = make(map[string]struct{})
	}
	var includesLastErr error

	mainFileContents = configIncludePattern.ReplaceAllFunc(mainFileContents, func(match []byte) []byte {
		if includesLastErr != nil {
			return nil
		}

View on GitHub (pinned to 91324e8de7)

Solutions

  1. Verify the printed path exists and is spelled correctly (absolute vs relative — relative includes resolve against the including file's directory)
  2. Check read permission on the file for the glance process user (ls -l, chmod/r chown as needed)
  3. In containers, confirm the volume actually mounts the file (docker inspect / exec ls)
  4. If the error names an include target, fix the path in the !include: directive

Example fix

# before
!include: ./shared/widgets.yml   # file lives in shared/parts/widgets.yml
# after
!include: ./shared/parts/widgets.yml
Defensive patterns

Strategy: validation

Validate before calling

// Verify readability before loading config
if info, err := os.Stat(cfgPath); err != nil || info.IsDir() {
    log.Fatalf("config path unusable: %v", err)
}

Try / catch

if err := loadConfig(path); err != nil {
    if errors.Is(err, fs.ErrNotExist) {
        log.Fatal("config or include file missing: ", err)
    }
    log.Fatal(err)
}

Prevention

When it happens

Trigger: Passing a config path to the reader where the file does not exist, is a directory, or the process lacks read permission; also when an !include: directive points at a missing or unreadable file.

Common situations: Running glance with a wrong -config path; Docker volume mount missing the config file; include paths that were valid on one machine but not another; file permissions after copying configs as a different user.

Related errors


AI-assisted analysis of glanceapp/glance@91324e8de7 (2026-08-15). Data as JSON: /api/errors/c38eec83b4bc2512. Report an issue: GitHub.