{"record":{"id":"4e8349d64bdfd963","repo":"slackhq/nebula","slug":"s-and-s-both-set-to-q-only-one-input-may-rea","errorCode":null,"errorMessage":"-%s and -%s both set to %q, only one input may read from stdin","messagePattern":"-(.+?) and -(.+?) both set to %q, only one input may read from stdin","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/nebula-cert/stdio.go","lineNumber":32,"sourceCode":"// help so the - convention is documented once instead of on every flag.\nconst stdioHelpText = \"  Pass \\\"-\\\" to any path flag to read from stdin or write to stdout.\\n\"\n\n// stdinReader is the source used when an input flag is set to \"-\".\n// It is a package level var so tests can swap in a deterministic reader.\n// Tests that mutate stdinReader cannot run with t.Parallel().\nvar stdinReader io.Reader = os.Stdin\n\n// ioClaims tracks which flags have claimed stdin and stdout during a single\n// command invocation so we can refuse a second flag asking for the same\n// stream.\ntype ioClaims struct {\n\tin  string\n\tout string\n}\n\nfunc (c *ioClaims) claimIn(flagName string) error {\n\tif c.in != \"\" && c.in != flagName {\n\t\treturn fmt.Errorf(\"-%s and -%s both set to %q, only one input may read from stdin\", c.in, flagName, stdioPath)\n\t}\n\tc.in = flagName\n\treturn nil\n}\n\nfunc (c *ioClaims) claimOut(flagName string) error {\n\tif c.out != \"\" && c.out != flagName {\n\t\treturn fmt.Errorf(\"-%s and -%s both set to %q, only one output may write to stdout\", c.out, flagName, stdioPath)\n\t}\n\tc.out = flagName\n\treturn nil\n}\n\n// reserveInputs walks alternating (flagName, path) pairs and claims stdin\n// for any path equal to stdioPath. It must be called before any input is\n// read so a conflict can be reported immediately instead of blocking on\n// io.ReadAll while waiting for input that will never arrive.\nfunc reserveInputs(claims *ioClaims, pairs ...string) error {","sourceCodeStart":14,"sourceCodeEnd":50,"githubUrl":"https://github.com/slackhq/nebula/blob/dd8f660c0ac37903ec4080ca4d3c861ba9342ceb/cmd/nebula-cert/stdio.go#L14-L50","documentation":"nebula-cert commands allow exactly one input flag to read from stdin ('-'). The ioClaims helper tracks which flag claimed stdin; claimIn returns this error when a second, different flag also requests stdin, since two inputs cannot both read the same stream.","triggerScenarios":"Calling claimIn (via openInput/readInput during sign/verify etc.) with a flag name different from the one that already claimed stdin — i.e. two input flags (e.g. -ca and -key, or -ca and -crt) are both set to '-'.","commonSituations":"User passes '-' for multiple input paths, e.g. -ca - -key -, intending to pipe both; script templating substituting empty paths with '-'.","solutions":["Set only ONE input flag to '-' and give the others real file paths","Concatenate PEM material (e.g. CA then key) into a single stdin stream only if the command supports it, otherwise use files","Fix script variables so paths aren't blank, which can default to '-'"],"exampleFix":"// before\nnebula-cert sign -ca - -key - -name host ...\n// after\nnebula-cert sign -ca ca.pem -key - -name host ... < ca.key","handlingStrategy":"validation","validationCode":"func validateInputs(paths map[string]string) error {\n    n := 0\n    for name, p := range paths {\n        if p == \"-\" { n++ }\n        _ = name\n    }\n    if n > 1 {\n        return fmt.Errorf(\"%d input flags read stdin; only one allowed\", n)\n    }\n    return nil\n}","typeGuard":null,"tryCatchPattern":"out, err := exec.Command(\"nebula-cert\", args...).CombinedOutput()\nif err != nil && strings.Contains(string(out), \"only one input may read from stdin\") {\n    log.Printf(\"fix stdin flags: %s\", out)\n}","preventionTips":["Never pass '-' to more than one input flag","In scripts, error on empty path variables instead of letting them become '-'","Pipe only the single material the command expects from stdin"],"tags":["go","cli","stdin","flag-conflict"],"backgroundTag":"ambiguous-stdin-input","analyzedSha":"dd8f660c0ac37903ec4080ca4d3c861ba9342ceb","analyzedAt":"2026-09-03T11:13:55.444Z","contentChangedAt":"2026-09-03T11:13:55.444Z","schemaVersion":2},"datasetVersion":"2026-09-10T17:17:09.494Z"}