googleapis/mcp-toolbox · error
unable to read tool file at %q: %w
Error message
unable to read tool file at %q: %w
What it means
During `toolbox migrate`, each resolved config file is read with os.ReadFile before conversion. If the file cannot be read (missing, permission denied, is a directory), this error is recorded per-file, the file is skipped, and migration continues with remaining files (cmd/internal/migrate/command.go:71-78). All per-file errors are joined and returned at the end.
Source
Thrown at cmd/internal/migrate/command.go:74
defer func() {
_ = shutdown(ctx)
}()
logger := opts.Logger
filePaths, _, err := opts.GetCustomConfigFiles(ctx)
if err != nil {
errMsg := fmt.Errorf("error retrieving configuration file: %w", err)
logger.ErrorContext(ctx, errMsg.Error())
return errMsg
}
logger.InfoContext(ctx, "migration process will start; any comments (except for top-level comments) presented in the original configuration files will not be preserved in the migrated files")
var errs []error
// process each files independently.
for _, filePath := range filePaths {
buf, err := os.ReadFile(filePath)
if err != nil {
errMsg := fmt.Errorf("unable to read tool file at %q: %w", filePath, err)
logger.ErrorContext(ctx, errMsg.Error())
errs = append(errs, errMsg)
continue
}
newBuf, err := internal.ConvertConfig(ctx, buf)
if err != nil {
logger.ErrorContext(ctx, err.Error())
errs = append(errs, err)
continue
}
if cmp.Equal(buf, newBuf) {
continue
}
if cmd.dryRun {
logger.DebugContext(ctx, fmt.Sprintf("printing migration to output for file: %s", filePath))
fmt.Fprintln(opts.IOStreams.Out, string(newBuf))
} else {View on GitHub (pinned to 8cc6e09de2)
Solutions
- Check the wrapped OS error after the message (no such file / permission denied) and fix accordingly.
- Confirm each --configs argument is an existing regular file (ls -l <path>), not a directory.
- Grant read permission: chmod u+r <file>, or run as a user with access.
- In containers, ensure the config file is mounted into the image/container at the expected path.
- Re-run migrate; the command continues with other files, so verify the specific file after fixing.
Example fix
// before $ toolbox migrate --configs ./configs toolbox: unable to read tool file at "configs": read configs: is a directory // after $ toolbox migrate --configs ./configs/tools.yaml
Defensive patterns
Strategy: validation
Validate before calling
// Pre-validate every config path is a readable regular file:
for _, p := range configPaths {
info, err := os.Stat(p)
if err != nil { return fmt.Errorf("cannot stat %q: %w", p, err) }
if info.IsDir() { return fmt.Errorf("%q is a directory, expected a file", p) }
if info.Mode().Perm()&0o400 == 0 { return fmt.Errorf("%q is not readable", p) }
} Try / catch
// migrate already collects per-file errors; surface and handle them jointly:
if err := runMigrate(); err != nil {
if strings.Contains(err.Error(), "unable to read tool file") {
// inspect each wrapped per-file read error via errors.Join/As
}
return err
} Prevention
- Pass file paths, never directories, to --configs.
- Ensure config files are mounted/copied into containers before running migrate.
- Check file permissions (chmod u+r) after checkouts or packaging steps.
- Run with --dry-run first; read failures are reported per-file while the rest still migrate.
When it happens
Trigger: A --configs path (or glob-expanded entry) that doesn't exist; a path that is a directory instead of a file; file present but the process lacks read permission; file deleted/renamed between resolution and read.
Common situations: Passing a directory to --configs expecting recursive behavior; symlink pointing to a removed file; running in a container where the config wasn't volume-mounted; restrictive file modes after a checkout or chmod.
Understand the failure class
Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.
Related errors
- error retrieving configuration file: %w
- error writing script %s: %w
- error writing SKILL.md: %w
- error converting config file: %s
- doc %d: unexpected non-string key in input: %v
AI-assisted analysis of googleapis/mcp-toolbox@8cc6e09de2 (2026-09-05).
Data as JSON: /api/errors/7bf85eb377b63a8a.
Report an issue: GitHub.