crowdsecurity/crowdsec · error
unable to symlink scenario '%s' to '%s': %w
Error message
unable to symlink scenario '%s' to '%s': %w
What it means
After copying the scenario into the runtime hub directory, installScenarioItem creates a symlink from the item-type directory (runtime/scenarios/<name>.yaml) to the hub copy. If os.Symlink fails for any reason other than the link already existing, this error wraps it. Existing symlinks are intentionally tolerated.
Source
Thrown at pkg/hubtest/scenario.go:39
// runtime/parsers/scenarios/
itemTypeDirDest := fmt.Sprintf("%s/scenarios/", t.RuntimePath)
if err := createDirs([]string{hubDirScenarioDest, itemTypeDirDest}); err != nil {
return err
}
// runtime/hub/scenarios/crowdsecurity/ssh-bf.yaml
hubDirScenarioPath := filepath.Join(hubDirScenarioDest, sourceFilename)
if err := Copy(sourcePath, hubDirScenarioPath); err != nil {
return fmt.Errorf("unable to copy '%s' to '%s': %w", sourcePath, hubDirScenarioPath, err)
}
// runtime/scenarios/ssh-bf.yaml
scenarioDirParserPath := filepath.Join(itemTypeDirDest, sourceFilename)
if err := os.Symlink(hubDirScenarioPath, scenarioDirParserPath); err != nil {
if !os.IsExist(err) {
return fmt.Errorf("unable to symlink scenario '%s' to '%s': %w", hubDirScenarioPath, scenarioDirParserPath, err)
}
}
return nil
}
func (t *HubTestItem) installScenarioCustomFrom(scenario string, customPath string) (bool, error) {
// we check if its a custom scenario
customScenarioPath := filepath.Join(customPath, scenario)
if _, err := os.Stat(customScenarioPath); os.IsNotExist(err) {
return false, nil
}
itemTypeDirDest := fmt.Sprintf("%s/scenarios/", t.RuntimePath)
if err := os.MkdirAll(itemTypeDirDest, os.ModePerm); err != nil {
return false, fmt.Errorf("unable to create folder '%s': %w", itemTypeDirDest, err)
}
View on GitHub (pinned to 909b515798)
Solutions
- Remove the stale non-symlink file at the destination path and re-run.
- On Windows, run with symlink privileges (developer mode/admin) or on a filesystem supporting symlinks.
- Check permissions on the runtime scenarios directory.
- Clean the test runtime directory (rm -rf) and re-run the tests.
Example fix
// before
os.WriteFile("/runtime/scenarios/ssh-bf.yaml", data, 0644) // later install tries to symlink same path
// after
os.RemoveAll("/runtime/scenarios/ssh-bf.yaml") // then os.Symlink succeeds Defensive patterns
Strategy: validation
Validate before calling
if fi, err := os.Lstat(dest); err == nil && fi.Mode()&os.ModeSymlink == 0 {
os.Remove(dest) // clear stale non-symlink before install
} Try / catch
if err := os.Symlink(target, dest); err != nil && !os.IsExist(err) {
var le *os.LinkError
if errors.As(err, &le) {
return fmt.Errorf("symlink unsupported or blocked at %s: %w", dest, err)
}
return err
} Prevention
- Clean the runtime test directory between runs (rm -rf runtime/).
- On Windows enable Developer Mode or run in WSL so symlinks work.
- Never write regular files into the runtime scenarios directory manually.
When it happens
Trigger: installScenario -> installScenarioItem where a symlink must be created at runtime/scenarios/<file> but fails: the destination path exists as a regular file/directory, or the filesystem doesn't support symlinks, or permission denied.
Common situations: A previous test run left a real file (not a symlink) at the destination; running on Windows without symlink privileges; read-only runtime directory.
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
- too many levels of symbolic links
- file '%s' is not from hub '%s' nor from the configuration di
- while creating symlink from %s to %s: %w
- can't get absolute path of '%s': %w
- unable to copy '%s' to '%s': %w
AI-assisted analysis of crowdsecurity/crowdsec@909b515798 (2026-09-06).
Data as JSON: /api/errors/50fd201227ce3ce3.
Report an issue: GitHub.