crowdsecurity/crowdsec · error

unable to symlink parser '%s' to '%s': %w

Error message

unable to symlink parser '%s' to '%s': %w

What it means

installParserItem creates a symlink in runtime/parsers/<stage>/ pointing to the hub copy; if os.Symlink fails with anything other than os.IsExist, it wraps the error. Typical causes are the destination parent directory missing or a filesystem that does not support symlinks.

Source

Thrown at pkg/hubtest/parser.go:40

	// runtime/parsers/s00-raw/
	itemTypeDirDest := fmt.Sprintf("%s/parsers/%s/", t.RuntimePath, item.Stage)

	if err := createDirs([]string{hubDirParserDest, itemTypeDirDest}); err != nil {
		return err
	}

	// runtime/hub/parsers/s00-raw/crowdsecurity/syslog-logs.yaml
	hubDirParserPath := filepath.Join(hubDirParserDest, sourceFilename)
	if err := Copy(sourcePath, hubDirParserPath); err != nil {
		return fmt.Errorf("unable to copy '%s' to '%s': %w", sourcePath, hubDirParserPath, err)
	}

	// runtime/parsers/s00-raw/syslog-logs.yaml
	parserDirParserPath := filepath.Join(itemTypeDirDest, sourceFilename)
	if err := os.Symlink(hubDirParserPath, parserDirParserPath); err != nil {
		if !os.IsExist(err) {
			return fmt.Errorf("unable to symlink parser '%s' to '%s': %w", hubDirParserPath, parserDirParserPath, err)
		}
	}

	return nil
}

func (t *HubTestItem) installParserCustomFrom(parser string, customPath string) (bool, error) {
	// we check if its a custom parser
	customParserPath := filepath.Join(customPath, parser)
	if _, err := os.Stat(customParserPath); os.IsNotExist(err) {
		return false, nil
	}

	customParserPathSplit, customParserName := filepath.Split(customParserPath)
	// because path is parsers/<stage>/<author>/parser.yaml and we wan't the stage
	splitPath := strings.Split(customParserPathSplit, string(os.PathSeparator))
	customParserStage := splitPath[len(splitPath)-3]

View on GitHub (pinned to 909b515798)

Solutions

  1. Remove the conflicting file at the destination and re-run: `rm runtime/parsers/<stage>/<parser>.yaml`.
  2. On Windows, enable Developer Mode or run the test with symlink privileges.
  3. Recreate the runtime tree so installParser creates all parent directories fresh.
  4. Verify the hub source path exists before symlinking (the copy step succeeded).

Example fix

// before
os.RemoveAll(runtimePath) // leaves stale files if it fails silently
runParserTests()
// after
if err := os.RemoveAll(runtimePath); err != nil {
	return fmt.Errorf("could not clean runtime dir: %w", err)
}
runParserTests()
Defensive patterns

Strategy: validation

Validate before calling

dst := filepath.Join(runtimePath, "parsers", stage, name+".yaml")
if fi, err := os.Lstat(dst); err == nil {
	_ = os.Remove(dst) // clear stale file/symlink before install
}
_ = fi

Try / catch

if err := item.InstallParser(name); err != nil {
	// on Windows, check symlink privilege / Developer Mode first
	return fmt.Errorf("symlink install failed for %s: %w", name, err)
}

Prevention

When it happens

Trigger: os.Symlink(hubDirParserPath, parserDirParserPath) returning an error other than EEXIST: parent dir runtime/parsers/<stage>/ not created, Windows without symlink privilege, stale non-symlink file at the destination path, permission denied.

Common situations: Running hubtests on Windows without developer mode/SeCreateSymbolicLinkPrivilege; a previous failed run left a regular file where the symlink goes; runtime tree partially deleted.

Understand the failure class

Background: "failed to write file", "Could not save figure", "Error saving remote file" — file write failed: causes and fixes across languages and libraries — this error's family across 38 libraries.

Related errors


AI-assisted analysis of crowdsecurity/crowdsec@909b515798 (2026-09-06). Data as JSON: /api/errors/3b78fe7ccea5422b. Report an issue: GitHub.