canopy-network/canopy · error

plugin launcher not found for %q; checked: %s

Error message

plugin launcher not found for %q; checked: %s

What it means

resolvePluginCtlPath searches known locations (working directory, executable-relative paths, and the source tree) for plugin/<name>/pluginctl.sh and returns this error when none of the candidates exists as a regular file. The message lists every path attempted so the missing location is diagnosable.

Source

Thrown at controller/controller.go:402

		candidates = append(candidates,
			filepath.Join(exeDir, relPath),
			filepath.Join(filepath.Dir(exeDir), relPath),
		)
	}
	// add a path relative to the source tree for local development
	if _, sourceFile, _, ok := runtime.Caller(0); ok {
		repoRoot := filepath.Dir(filepath.Dir(sourceFile))
		candidates = append(candidates, filepath.Join(repoRoot, relPath))
	}
	// return the first existing file path
	for _, candidate := range candidates {
		info, err := os.Stat(candidate)
		if err == nil && !info.IsDir() {
			return candidate, nil
		}
	}
	// exit with a descriptive error containing all attempted paths
	return "", fmt.Errorf("plugin launcher not found for %q; checked: %s", plugin, strings.Join(candidates, ", "))
}

// INTERNAL CALLS BELOW

// LoadIsOwnRoot() returns if this chain is its own root (base)
func (c *Controller) LoadIsOwnRoot() (isOwnRoot bool) {
	// use the state machine to check if this chain is the root chain
	isOwnRoot, err := c.FSM.LoadIsOwnRoot()
	// if an error occurred
	if err != nil {
		// log the error
		c.log.Error(err.Error())
	}
	// exit
	return
}

// RootChainId() returns the root chain id according to the FSM

View on GitHub (pinned to ee8197d91d)

Solutions

  1. Check the 'checked:' path list in the error and create/copy pluginctl.sh at one of those exact locations
  2. Run the binary from the repository root where ./plugin/<name>/pluginctl.sh exists
  3. Verify the plugin name is spelled correctly (it maps to a plugin subdirectory)
  4. Install the plugin assets next to the executable (exe_dir/plugin/<name>/pluginctl.sh) for deployed binaries

Example fix

// before
$ ./bin/canopyd plugin-enable indexer   # run from $HOME
// after
$ cd /path/to/repo && ./bin/canopyd plugin-enable indexer
# or install assets:
$ mkdir -p bin/plugin/indexer && cp plugin/indexer/pluginctl.sh bin/plugin/indexer/
Defensive patterns

Strategy: validation

Validate before calling

for _, dir := range candidateDirs() {
    p := filepath.Join(dir, "plugin", pluginName, "pluginctl.sh")
    if info, err := os.Stat(p); err == nil && !info.IsDir() { return p, nil }
}
return "", fmt.Errorf("pluginctl.sh for %q missing; run make install-plugins", pluginName)

Try / catch

path, err := resolvePluginCtlPath(plugin)
if err != nil {
    log.Fatalf("plugin install problem: %v", err) // error lists all checked paths
}

Prevention

When it happens

Trigger: runPluginCtl invoking a plugin whose pluginctl.sh is not installed in any of the checked locations: ./plugin/<plugin>/pluginctl.sh, relative to the executable dir, its parent, or the repo root.

Common situations: Running a compiled binary outside the repo without copying the plugin directory, running from the wrong working directory, a plugin name typo, or an incomplete build/install that omitted the plugin assets.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


AI-assisted analysis of canopy-network/canopy@ee8197d91d (2026-09-06). Data as JSON: /api/errors/f8a94d1594200e3c. Report an issue: GitHub.