This bug description was generated with the help of Claude
Describe the bug
get_cmdstan_flags("STANCFLAGS") splits the flag string on whitespace (R/utils.R):
flags_vec <- strsplit(x = trimws(flags), split = "\s+", perl = TRUE)[[1]]
That splits inside a quoted value. A make/local containing
STANCFLAGS += --include-paths='/my dir'
becomes:
#> [1] "--include-paths='/my" "dir'"
compile() appends this vector to the stanc options and then uses it two ways:
- for make, the elements are rejoined with spaces into STANCFLAGS += ..., which round-trips fine — the shell re-strips the quotes;
- for the direct stanc calls in get_standalone_hpp() (which relements are passed to processx as separate arguments, so stanc receives --include-paths='/my and dir' as two distinct argv e
stanc then treats dir' as a positional argument:
stanc: too many arguments, don't know what to do with model.s
Only compile() is affected — $check_syntax() and $format() don't read local STANCFLAGS.
To Reproduce
Put a quoted, space-containing value in make/local:
STANCFLAGS += --include-paths='/my dir'
then compile any model. The equivalent failure can be seen directly:
stanc <- file.path(cmdstan_path(), "bin", "stanc")
processx::run(stanc, c("--include-paths='/my", "dir'", "model.stan"),
error_on_status = FALSE)$stderr
#> stanc: too many arguments, don't know what to do with model.stan
Expected behavior
Two things, because #1254 changes what this example may do at all.
Under #1254 §6, --include-paths in make/local's STANCFLAGS is refused at build time ("--include-paths in the effective STANCFLAGS, below" is one of the rejected channels, since a path supplied there resolves for the build and not for the stanc --info call the assessment is built on). So for the example above the expected result is that error, and the reproduction becomes a rejection test.
The splitting defect is independent of that and still has to be fixed: flags read from make/local must reach direct stanc calls the way the shell would deliver them, one argument per flag with the quoting removed. The fixture is a permitted valued flag with a space in its value, and --filename-in-msg is the one that exists:
STANCFLAGS += --filename-in-msg='/my dir/model.stan'
At stage 1 that must reach direct stanc as the single argument --filename-in-msg=/my dir/model.stan. At stage 4, when cmdstanr injects --filename-in-msg itself, the same fixture asserts the make/local occurrence is dropped whole under #1254 §6's rule that a flag the call emits wins over the same flag in make/local, with no stray dir/model.stan' element left behind. That drop matches on the split vector, which is why the tokenizer has to be right before it.
Additional context
Pre-existing, not a regression. This is the same class of bug as #820 and #1227 (shell quoting leaking into processx arguments), but for flags coming from make/local rather than from include_paths or stanc_options. #1231 splits the direct and make-quoted option vectors but keeps passing the split local flags to both, so it doesn't cover this case.
Not a duplicate of #1230, which is the mirror image: #1230 is about the quoting cmdstanr writes for Make in include_paths_stanc3_args() and explicitly does not affect direct stanc calls. This one is about the quoting cmdstanr reads from make/local in get_cmdstan_flags(), and affects only the direct calls. Neither fix resolves the other — flags from make/local never pass through include_paths_stanc3_args().
This bug description was generated with the help of Claude
Describe the bug
get_cmdstan_flags("STANCFLAGS") splits the flag string on whitespace (R/utils.R):
flags_vec <- strsplit(x = trimws(flags), split = "\s+", perl = TRUE)[[1]]
That splits inside a quoted value. A make/local containing
STANCFLAGS += --include-paths='/my dir'
becomes:
#> [1] "--include-paths='/my" "dir'"
compile() appends this vector to the stanc options and then uses it two ways:
stanc then treats dir' as a positional argument:
stanc: too many arguments, don't know what to do with model.s
Only compile() is affected — $check_syntax() and $format() don't read local STANCFLAGS.
To Reproduce
Put a quoted, space-containing value in make/local:
STANCFLAGS += --include-paths='/my dir'
then compile any model. The equivalent failure can be seen directly:
Expected behavior
Two things, because #1254 changes what this example may do at all.
Under #1254 §6,
--include-pathsinmake/local'sSTANCFLAGSis refused at build time ("--include-pathsin the effectiveSTANCFLAGS, below" is one of the rejected channels, since a path supplied there resolves for the build and not for thestanc --infocall the assessment is built on). So for the example above the expected result is that error, and the reproduction becomes a rejection test.The splitting defect is independent of that and still has to be fixed: flags read from
make/localmust reach direct stanc calls the way the shell would deliver them, one argument per flag with the quoting removed. The fixture is a permitted valued flag with a space in its value, and--filename-in-msgis the one that exists:At stage 1 that must reach direct stanc as the single argument
--filename-in-msg=/my dir/model.stan. At stage 4, when cmdstanr injects--filename-in-msgitself, the same fixture asserts themake/localoccurrence is dropped whole under #1254 §6's rule that a flag the call emits wins over the same flag inmake/local, with no straydir/model.stan'element left behind. That drop matches on the split vector, which is why the tokenizer has to be right before it.Additional context
Pre-existing, not a regression. This is the same class of bug as #820 and #1227 (shell quoting leaking into processx arguments), but for flags coming from make/local rather than from include_paths or stanc_options. #1231 splits the direct and make-quoted option vectors but keeps passing the split local flags to both, so it doesn't cover this case.
Not a duplicate of #1230, which is the mirror image: #1230 is about the quoting cmdstanr writes for Make in include_paths_stanc3_args() and explicitly does not affect direct stanc calls. This one is about the quoting cmdstanr reads from make/local in get_cmdstan_flags(), and affects only the direct calls. Neither fix resolves the other — flags from make/local never pass through include_paths_stanc3_args().