stan-dev / stan-dev/cmdstanr

Record what an executable was built with, alongside the executable?

Open
#1,238 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

feature
Dominant language
R
Stars
160
Forks
69
Avg merge
1d 19h
Merged PRs (30d)
15

Description

OPEN

Record what an executable was built with, alongside the executable?

Note: While working on #1235 I was reminded of several longstanding issues. I asked Claude to turn them into reports. This body has been rewritten to reflect the design settled in #1254. The original version and the review that followed are in this issue's edit history.


CmdStanR cannot tell what produced an existing executable. The binary reports a handful of STAN_* flags via <exe> info and nothing else. The user header, the include paths and the rest of cpp_options are not reported. Anything else lives only in the R object, so it is gone in the next session.

The fix is a JSON record written beside the executable at build time, .<exe>.cmdstanr.json. This issue is the record itself; #1255 is what acts on it. The contract is #1254 §4 ("Contract: the build record"), whose recorded/compared table is the single statement of what the record holds and which fields drive a rebuild. This issue does not restate it.

The live bug this fixes

R/model.R:732-733 asks Make's question:

file.mtime(exe) < file.mtime(self$stan_file())

That is exe versus source, correct only if mtimes move monotonically forward. Extracting a project tarball over an existing build:

source content is now DIFFERENT
exe mtime : 2026-08-27 10:19:38.476
stan mtime: 2026-03-15 09:00:00.000
exe < stan (rebuild?): FALSE   <-- no rebuild

cmdstanr reports "up to date" and runs the old binary against the new source. tar -x, unzip, cp -p, rsync -a and backup restores all preserve archival mtimes and all reach this. It is filed here rather than separately because it cannot be fixed before a record exists.

Stages (#1258 holds the full stage lists)

Stage 2: schema and helper tests. Behaviour-free: nothing writes a record beside a user's program until stage 3.

  • Parser, writer and comparison helpers tested against fixtures; every required field validated in the parser, not in each caller (#1254 §4, "The record is parsed into an object, and compared as one")
  • The executable hash bond, corruption, and an unsupported format_version (#1254 §4, "Binding the record to its executable"; "Format versions")
  • reported_features tri-state round trip, encoded by key presence (#1254 §1)
  • Ignore patterns in this repository before anything writes a record beside a test model (#1254 §4, "The record's lifecycle follows the executable's")

Stage 3: transactional record writing.

  • Stage executable and record together, install both, verify the pair, roll back both on any failure (#1254 §4, "Binding the record to its executable"). Locking and TOCTOU are out of scope for v1; #1025 is related and open.
  • Populate every field in #1254 §4's table, including request.stanc_name, tbb_dir (from the call's cpp_options, not asked of make) and known_untracked_dependencies (#1257)
  • Accumulate injected stanc options in their own list rather than merging them into the caller's (#1254 §10)
  • Document the lifecycle rule where users will look for it: whatever ignores the executable ignores the record

Measurements

  • Hashing the executable: 8.5 ms, against 28.6 ms merely to spawn <exe> info.
  • Hashing six realistic source files: 0.31 ms, against a rebuild floor of 6.7 s for bernoulli.stan with precompiled headers and 13.8 s without (#1254 §4, "Hashes rather than mtimes").

Also settled

  • Absorbs #1247: canonical option spelling is part of the record format, not a separate display fix.
  • Executable-only models (cmdstan_model(exe_file =)) with a valid hash-bound record have known provenance; without one they are explicitly unprovenanced. Never automatically rebuilt (#1254 §7).

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with R/model.R:732-733 to understand the mtime-based decision, then read contract #1254 §4 and the staged requirements in this issue. Review the parser, writer, comparison helpers, and transactional executable/record lifecycle already described as complete. Done means the record follows the contract, binds to its executable, and prevents stale-source decisions without breaking the stated exclusions.

Written by the indexing model from the issue text.

Assessment

Tech stack
r
Domain
build-system
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.