Skip to content

Quoted values in make/local STANCFLAGS are split on whitespace and break direct stanc calls #1232

Description

@jgabry

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().

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions