{"record":{"id":"7bf85eb377b63a8a","repo":"googleapis/mcp-toolbox","slug":"unable-to-read-tool-file-at-q-w","errorCode":null,"errorMessage":"unable to read tool file at %q: %w","messagePattern":"unable to read tool file at %q: %w","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/internal/migrate/command.go","lineNumber":74,"sourceCode":"\tdefer func() {\n\t\t_ = shutdown(ctx)\n\t}()\n\n\tlogger := opts.Logger\n\tfilePaths, _, err := opts.GetCustomConfigFiles(ctx)\n\tif err != nil {\n\t\terrMsg := fmt.Errorf(\"error retrieving configuration file: %w\", err)\n\t\tlogger.ErrorContext(ctx, errMsg.Error())\n\t\treturn errMsg\n\t}\n\n\tlogger.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\")\n\tvar errs []error\n\t// process each files independently.\n\tfor _, filePath := range filePaths {\n\t\tbuf, err := os.ReadFile(filePath)\n\t\tif err != nil {\n\t\t\terrMsg := fmt.Errorf(\"unable to read tool file at %q: %w\", filePath, err)\n\t\t\tlogger.ErrorContext(ctx, errMsg.Error())\n\t\t\terrs = append(errs, errMsg)\n\t\t\tcontinue\n\t\t}\n\t\tnewBuf, err := internal.ConvertConfig(ctx, buf)\n\t\tif err != nil {\n\t\t\tlogger.ErrorContext(ctx, err.Error())\n\t\t\terrs = append(errs, err)\n\t\t\tcontinue\n\t\t}\n\t\tif cmp.Equal(buf, newBuf) {\n\t\t\tcontinue\n\t\t}\n\n\t\tif cmd.dryRun {\n\t\t\tlogger.DebugContext(ctx, fmt.Sprintf(\"printing migration to output for file: %s\", filePath))\n\t\t\tfmt.Fprintln(opts.IOStreams.Out, string(newBuf))\n\t\t} else {","sourceCodeStart":56,"sourceCodeEnd":92,"githubUrl":"https://github.com/googleapis/mcp-toolbox/blob/8cc6e09de2ad7b8bffc77751799585a1401a48eb/cmd/internal/migrate/command.go#L56-L92","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\n$ toolbox migrate --configs ./configs\ntoolbox: unable to read tool file at \"configs\": read configs: is a directory\n// after\n$ toolbox migrate --configs ./configs/tools.yaml","handlingStrategy":"validation","validationCode":"// Pre-validate every config path is a readable regular file:\nfor _, p := range configPaths {\n    info, err := os.Stat(p)\n    if err != nil { return fmt.Errorf(\"cannot stat %q: %w\", p, err) }\n    if info.IsDir() { return fmt.Errorf(\"%q is a directory, expected a file\", p) }\n    if info.Mode().Perm()&0o400 == 0 { return fmt.Errorf(\"%q is not readable\", p) }\n}","typeGuard":null,"tryCatchPattern":"// migrate already collects per-file errors; surface and handle them jointly:\nif err := runMigrate(); err != nil {\n    if strings.Contains(err.Error(), \"unable to read tool file\") {\n        // inspect each wrapped per-file read error via errors.Join/As\n    }\n    return err\n}","preventionTips":["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."],"tags":["cli","filesystem","migration"],"backgroundTag":"file-read-failed","analyzedSha":"8cc6e09de2ad7b8bffc77751799585a1401a48eb","analyzedAt":"2026-09-05T01:10:36.887Z","contentChangedAt":"2026-09-05T01:10:36.887Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}