{"record":{"id":"75aa22463c1e7cae","repo":"hashicorp/nomad","slug":"intro-token-file-is-a-directory","errorCode":null,"errorMessage":"intro token file is a directory","messagePattern":"intro token file is a directory","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"command/agent/agent.go","lineNumber":879,"sourceCode":"\n\trootFile, err := os.OpenInRoot(cfg.StateDir, \"intro_token.jwt\")\n\tif err != nil {\n\t\tif os.IsNotExist(err) {\n\t\t\treturn nil\n\t\t}\n\t\treturn err\n\t}\n\n\tfileStat, err := rootFile.Stat()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to stat intro token file: %w\", err)\n\t}\n\n\t// If the file exists and is a file, attempt to read the contents and set\n\t// the intro token. Any error is logged for the operator to investigate but\n\t// does not block the agent from starting.\n\tif fileStat.IsDir() {\n\t\treturn fmt.Errorf(\"intro token file is a directory\")\n\t}\n\n\tcontent, err := helper.ReadFileContent(rootFile)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to read intro token file: %w\", err)\n\t}\n\n\tcfg.IntroToken = strings.TrimSpace(string(content))\n\treturn nil\n}\n\n// convertClientConfig takes an agent config and log output and returns a client\n// Config. There may be missing fields that must be set by the agent. To do this\n// call finalizeServerConfig\nfunc convertClientConfig(agentConfig *Config) (*clientconfig.Config, error) {\n\t// Set up the configuration\n\tconf := agentConfig.ClientConfig\n\tif conf == nil {","sourceCodeStart":861,"sourceCodeEnd":897,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/command/agent/agent.go#L861-L897","documentation":"After stat succeeds, readIntroTokenFile checks fileStat.IsDir(); if the intro token path is a directory, it returns this plain error because a directory cannot hold token content. The intro token must be a regular file whose contents are the token.","triggerScenarios":"client.intro_token_file pointing at a directory (e.g. a secrets mount point like /var/run/secrets/ or the token's parent directory instead of the file), invoked from finalizeClientConfig.","commonSituations":"Kubernetes secret volume mounts where the user gives the volume dir rather than the key file inside it; path expansion mistakes in config tooling; mounted paths that are always directories.","solutions":["Point intro_token_file at the actual regular file, not its parent directory.","For volume-mounted secrets, reference the key file inside the mount (e.g. /secrets/intro-token, not /secrets).","Pre-provision a file at the path if the mount point is a directory by design."],"exampleFix":"// before\nclient { intro_token_file = \"/var/run/secrets/nomad\" } // directory\n// after\nclient { intro_token_file = \"/var/run/secrets/nomad/intro-token\" }","handlingStrategy":"validation","validationCode":"fi, err := os.Stat(path)\nif err != nil {\n  return err\n}\nif fi.IsDir() {\n  return fmt.Errorf(\"intro_token_file %q is a directory; point to the token file itself\", path)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Reference the key file inside mounted secret volumes, not the mount root.","Add a pre-start check that the path is a regular file (fi.Mode().IsRegular()).","Avoid directory-typed config variables for file paths."],"tags":["filesystem","nomad","config"],"backgroundTag":"path-is-a-directory","analyzedSha":"482b49bf1aec006f089bcfc7e632d8f6ac303e5e","analyzedAt":"2026-09-04T07:54:14.808Z","contentChangedAt":"2026-09-04T07:54:14.808Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}