chenhg5/cc-connect · error

write plist: %w

Error message

write plist: %w

What it means

After building the launchd plist, Install writes it to the LaunchAgents path with os.WriteFile at 0600 and then chmods to 0600 (since WriteFile only applies perm on create). A failure writing the file is wrapped as 'write plist:'. This is the final persistence step for the service definition, so install aborts without registering the job.

Source

Thrown at daemon/launchd.go:64

		return fmt.Errorf("create LaunchAgents dir: %w", err)
	}
	if err := os.MkdirAll(filepath.Dir(cfg.LogFile), 0755); err != nil {
		return fmt.Errorf("create log dir: %w", err)
	}

	// Unload existing service first (ignore errors) so we do not leave a stale
	// job behind when switching between GUI and headless sessions.
	bootoutLaunchdTargets()

	plist := buildPlist(cfg)
	// 0600: plist may contain captured secret values (config.toml ${ENV}
	// placeholders and any EnvDiscoverer extension output). User-only
	// LaunchAgents path; root can still read but that is the user's own
	// machine boundary. os.WriteFile only applies perm on create, so
	// Chmod afterwards is required to harden reinstalls of files that
	// pre-existed at 0644 from earlier cc-connect versions.
	if err := os.WriteFile(plistPath, []byte(plist), 0600); err != nil {
		return fmt.Errorf("write plist: %w", err)
	}
	if err := os.Chmod(plistPath, 0600); err != nil {
		return fmt.Errorf("chmod plist: %w", err)
	}

	domain := preferredLaunchdDomain()
	if out, err := runLaunchctl("bootstrap", domain, plistPath); err != nil {
		return fmt.Errorf("launchctl bootstrap: %s (%w)", out, err)
	}

	if _, err := runLaunchctl("kickstart", "-kp", launchdTarget(domain)); err != nil {
		return fmt.Errorf("launchctl kickstart: %w", err)
	}
	return nil
}

func (m *launchdManager) Uninstall() error {
	bootoutLaunchdTargets()

View on GitHub (pinned to 4000b2338a)

Solutions

  1. Check ownership of the existing plist and chown/remove it: ls -l ~/Library/LaunchAgents/<plist>; sudo rm if owned by root
  2. Free disk space if the cause is ENOSPC
  3. Re-run install as the same user that owns the LaunchAgents directory
  4. Apply the wrapped %w cause (os error) to identify permission vs. space issues

Example fix

// before
$ cc-connect daemon install  // plist owned by root
// after
$ sudo rm ~/Library/LaunchAgents/com.cc-connect.plist
$ cc-connect daemon install
Defensive patterns

Strategy: try-catch

Validate before calling

if st, err := os.Stat(plistPath); err == nil {
    if fi, _ := st.Info(); fi != nil && !isOwnedByCurrentUser(fi) {
        return fmt.Errorf("plist %s not owned by current user", plistPath)
    }
}
if err := unix.Access(filepath.Dir(plistPath), unix.W_OK); err != nil {
    return fmt.Errorf("LaunchAgents dir not writable: %w", err)
}

Try / catch

if err := daemon.Install(cfg); err != nil {
    var pe *fs.PathError
    if errors.As(err, &pe) && errors.Is(pe.Err, os.ErrPermission) {
        // chown/remove the stale plist, then retry install
    }
    return err
}

Prevention

When it happens

Trigger: os.WriteFile(plistPath, plist, 0600) fails — disk full, permission denied on an existing plist owned by another user, read-only volume, or the LaunchAgents path became unwritable between mkdir and write.

Common situations: An old plist created by root that the current user cannot overwrite; quota/disk-full; antivirus blocking writes into LaunchAgents; installing as a different user than the previous install.

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 chenhg5/cc-connect@4000b2338a (2026-09-06). Data as JSON: /api/errors/ef1bd02207e25c78. Report an issue: GitHub.