{"record":{"id":"2a7f15c3d7629968","repo":"fish-shell/fish-shell","slug":"s-expected-a-numeric-value","errorCode":null,"errorMessage":"%s: expected a numeric value","messagePattern":"(.+?): expected a numeric value","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/builtins/printf.rs","lineNumber":219,"sourceCode":"\nimpl<'a, 'b> State<'a, 'b> {\n    #[allow(clippy::partialeq_to_none)]\n    fn verify_numeric(&mut self, s: &wstr, end: &wstr, errcode: Option<wutil::Error>) {\n        // This check matches the historic `errcode != EINVAL` check from C++.\n        // Note that empty or missing values will be silently treated as 0.\n        if errcode.is_some_and(|err| err != wutil::Error::InvalidChar && err != wutil::Error::Empty)\n        {\n            match errcode.unwrap() {\n                wutil::Error::Overflow => {\n                    self.fatal_error(err_fmt!(\"%s: Number out of range\", s));\n                }\n                wutil::Error::InvalidChar | wutil::Error::Empty => {\n                    unreachable!(\"Unreachable\");\n                }\n            }\n        } else if !end.is_empty() {\n            if s.as_ptr() == end.as_ptr() {\n                self.fatal_error(err_fmt!(\"%s: expected a numeric value\", s));\n            } else {\n                // This isn't entirely fatal - the value should still be printed.\n                self.nonfatal_error(err_fmt!(\n                    \"%s: value not completely converted (can't convert '%s')\",\n                    s,\n                    end\n                ));\n                // Warn about octal numbers as they can be confusing.\n                // Do it if the unconverted digit is a valid hex digit,\n                // because it could also be an \"0x\" -> \"0\" typo.\n                if s.char_at(0) == '0' && iswxdigit(end.char_at(0)) {\n                    self.nonfatal_error(err_str!(\n                        \"Hint: a leading '0' without an 'x' indicates an octal number\"\n                    ));\n                }\n            }\n        }\n    }","sourceCodeStart":201,"sourceCodeEnd":237,"githubUrl":"https://github.com/fish-shell/fish-shell/blob/a1e92997a1479a5fbb29587ee805e4b239cdd001/src/builtins/printf.rs#L201-L237","documentation":"Raised by the printf builtin (src/builtins/printf.rs:219) in verify_numeric when nothing at all was consumed from the argument: the end slice still points at the start of s (s.as_ptr() == end.as_ptr()) and no converter error code was set. It means the argument to a numeric directive (%d, %f, %x, ...) does not begin with anything numeric. This is fatal: printf stops processing and exits with an error status.","triggerScenarios":"Running `printf %d abc`, `printf %f NaN_variable`, or `printf %x 0x` where the parse consumes zero characters. Reached when errcode is None/InvalidChar/Empty but end is non-empty and unchanged; i.e. wcstoi_partial/wcstod returned consumed == 0 with no hard error. Note the leading-quote form `printf %f \"'a\"` bypasses this via from_ord.","commonSituations":"Unquoted or wrongly-parsed variables feeding printf format strings; scripts assuming an environment variable always holds a number; user input not validated; a subtle case is locale issues with decimal separators for %f, though fish retries with '.' before giving up (see the wcstod fallback in RawStringToScalarType).","solutions":["Validate the value before printf: `string match -qr '^-?[0-9]+(\\.[0-9]+)?$' -- $val` and reject/skip otherwise","Use fish's `math` builtin or `read` with validation instead of printf numeric coercion for untrusted input","Print untrusted data with %s and only use numeric directives for values your script produced","Check the variable is set and non-empty first: empty values are silently treated as 0, which can mask upstream bugs"],"exampleFix":"# before\nprintf '%d' $maybe_number   # abc: expected a numeric value\n\n# after\nif string match -qr -- '^-?[0-9]+$' $maybe_number\n    printf '%d' $maybe_number\nelse\n    echo \"not a number: $maybe_number\" >&2\nend","handlingStrategy":"validation","validationCode":"# only hand provably-numeric strings to numeric directives\nif string match -qr -- '^[+-]?[0-9]+(\\.[0-9]+)?([eE][+-]?[0-9]+)?$' $value\n    printf '%f\\n' $value\nelse\n    echo \"skipping non-numeric: $value\" >&2\nend","typeGuard":"function is_numeric\n    string match -qr -- '^[+-]?([0-9]+(\\.[0-9]*)?|\\.[0-9]+)([eE][+-]?[0-9]+)?$' $argv[1]\nend","tryCatchPattern":null,"preventionTips":["Validate externally-sourced values with string match -r before printf","Remember empty strings are silently treated as 0 — check set -q separately if empty is an error","Prefer %s plus your own formatting when the input's shape is unknown"],"tags":["printf","parse","numeric","shell"],"backgroundTag":"string-to-number-conversion-failed","analyzedSha":"a1e92997a1479a5fbb29587ee805e4b239cdd001","analyzedAt":"2026-08-17T10:54:24.402Z","contentChangedAt":"2026-08-17T10:54:24.402Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}