googleapis/mcp-toolbox · error

error reading config folder %w

Error message

error reading config folder %w

What it means

`scanWatchedFiles` throws "error reading config folder %w" when `os.ReadDir(folderToWatch)` fails while scanning the watched config directory for changed .yaml/.yml files during dynamic reload. This is a filesystem-level failure — the folder could not be listed at all — and wraps the underlying os error (which names the reason: not found, permission denied, etc.). The caller `watchChanges` receives the error and can no longer detect config changes.

Source

Thrown at cmd/root.go:199

}

// Helper to check if a file has a newer ModTime than stored in the map
func checkModTime(path string, mTime time.Time, lastSeen map[string]time.Time) bool {
	if mTime.After(lastSeen[path]) {
		lastSeen[path] = mTime
		return true
	}
	return false
}

// Helper to scan watched files and check their modification times in polling system
func scanWatchedFiles(watchingFolder bool, folderToWatch string, watchedFiles map[string]bool, lastSeen map[string]time.Time) (map[string]bool, bool, error) {
	changed := false
	currentDiskFiles := make(map[string]bool)
	if watchingFolder {
		files, err := os.ReadDir(folderToWatch)
		if err != nil {
			return nil, changed, fmt.Errorf("error reading config folder %w", err)
		}
		for _, f := range files {
			if !f.IsDir() && (strings.HasSuffix(f.Name(), ".yaml") || strings.HasSuffix(f.Name(), ".yml")) {
				fullPath := filepath.Join(folderToWatch, f.Name())
				currentDiskFiles[fullPath] = true
				if info, err := f.Info(); err == nil {
					if checkModTime(fullPath, info.ModTime(), lastSeen) {
						changed = true
					}
				}
			}
		}
	} else {
		for f := range watchedFiles {
			if info, err := os.Stat(f); err == nil {
				currentDiskFiles[f] = true
				if checkModTime(f, info.ModTime(), lastSeen) {
					changed = true

View on GitHub (pinned to 8cc6e09de2)

Solutions

  1. Read the wrapped OS error — it states whether the path is missing, permission-denied, or not a directory
  2. Verify `folderToWatch` exists and is a directory: `ls -la <folder>`
  3. Restore read permission on the folder for the toolbox process user (`chmod/chown`)
  4. If the folder was deleted/moved, recreate it or restart the server pointing at the correct path
  5. For containers, ensure the config volume mount is present and not read-removed at runtime

Example fix

// before: server started watching a folder that was later renamed
toolbox serve --config-folder ./configs   // ./configs deleted at runtime
// after: recreate the folder or restart against the right path
mkdir -p ./configs && toolbox serve --config-folder ./configs
Defensive patterns

Strategy: validation

Validate before calling

// Check the watch folder exists and is readable before starting the server
func watchable(dir string) error {
	info, err := os.Stat(dir)
	if err != nil { return err }
	if !info.IsDir() { return fmt.Errorf("%s is not a directory", dir) }
	f, err := os.Open(dir)
	if err != nil { return err }
	return f.Close()
}

Try / catch

files, err := os.ReadDir(folderToWatch)
if err != nil {
	return nil, changed, fmt.Errorf("error reading config folder %w", err)
}

Prevention

When it happens

Trigger: `toolbox serve` is run with folder watching enabled (watching a directory rather than a single file) and the directory was deleted, renamed, is on an unmounted volume, or the process lacks read permission on it; `scanWatchedFiles` is invoked on each watch tick.

Common situations: Config directory moved/deleted while the server runs; running in a container where the config dir mount disappeared; permission changes (chmod/chown) after startup; pointing `--watch-folder` (or equivalent) at a path that doesn't exist.

Related errors


AI-assisted analysis of googleapis/mcp-toolbox@8cc6e09de2 (2026-09-05). Data as JSON: /api/errors/09103cf874d8a252. Report an issue: GitHub.