wagoodman/dive · error

failed to read CI config file %s: %w

Error message

failed to read CI config file %s: %w

What it means

Thrown while loading the CI rules file when os.ReadFile on the user-supplied --ci-config path fails after fileExists() reported it present. The %w preserves the OS error. It only fires when ConfigPath is non-empty and the file exists at check time, so failures are typically transient or permission-related.

Source

Thrown at cmd/dive/cli/internal/options/ci.go:55

func (c *CI) AddFlags(flags clio.FlagSet) {
	flags.BoolVarP(&c.Enabled, "ci", "", "skip the interactive TUI and validate against CI rules (same as env var CI=true)")
	flags.StringVarP(&c.ConfigPath, "ci-config", "", "if CI=true in the environment, use the given yaml to drive validation rules.")
}

func (c *CI) PostLoad() error {
	enabledFromEnv := truthy(os.Getenv("CI"))
	if !c.Enabled && enabledFromEnv {
		c.Enabled = true
	}

	if c.ConfigPath != "" {
		if fileExists(c.ConfigPath) {
			// if a config file is provided, load it and override any values provided in the application config.
			// If we're hitting this case we should pretend that only the config file was provided and applied
			// on top of the default config values.
			yamlFile, err := os.ReadFile(c.ConfigPath)
			if err != nil {
				return fmt.Errorf("failed to read CI config file %s: %w", c.ConfigPath, err)
			}
			def := DefaultCIRules()
			r := legacyRuleFile{
				LowestEfficiencyThresholdString: def.LowestEfficiencyThresholdString,
				HighestWastedBytesString:        def.HighestWastedBytesString,
				HighestUserWastedPercentString:  def.HighestUserWastedPercentString,
			}
			wrapper := struct {
				Rules *legacyRuleFile `yaml:"rules"`
			}{
				Rules: &r,
			}
			if err := yaml.Unmarshal(yamlFile, &wrapper); err != nil {
				return fmt.Errorf("failed to unmarshal CI config file %s: %w", c.ConfigPath, err)
			}
			// TODO: should this be a deprecated use warning in the future?
			c.Rules = CIRules{
				LowestEfficiencyThresholdString: r.LowestEfficiencyThresholdString,

View on GitHub (pinned to d6c691947f)

Solutions

  1. Inspect the wrapped error for the exact OS cause ('permission denied', 'no such file or directory').
  2. Fix permissions: chmod 644 <path> and ensure the running user owns or can read it.
  3. If the file is generated earlier in the pipeline, verify that step succeeded before invoking dive.
  4. Remove the --ci-config flag to fall back to the built-in default CI rules if the file is optional.

Example fix

# before
chmod 600 ci-rules.yaml && sudo -u nobody dive image --ci --ci-config ci-rules.yaml  # read fails

# after
chmod 644 ci-rules.yaml && dive image --ci --ci-config ci-rules.yaml
Defensive patterns

Strategy: validation

Validate before calling

// wrapper check before invoking dive
if cfgPath != "" {
    if _, err := os.ReadFile(cfgPath); err != nil {
        return fmt.Errorf("ci config unreadable, fix perms or drop flag: %w", err)
    }
}

Prevention

When it happens

Trigger: Passing --ci-config <path> where the file exists but cannot be opened: permission denied (mode 000, wrong owner), a race where the file is deleted between the stat and read, or a symlink pointing to an unreadable target.

Common situations: CI jobs where the rules file is generated by a prior step with restrictive permissions; Docker volume-mounted configs owned by a different UID; TOCTOU deletion in shared workspaces.

Related errors


AI-assisted analysis of wagoodman/dive@d6c691947f (2026-08-15). Data as JSON: /api/errors/c74d0fc9ddf57cb1. Report an issue: GitHub.