pydantic/monty · error

failed to write

Error message

failed to write {}: {e}

What it means

After rewriting version pins in packages/pydantic-monty/pyproject.toml, the build script writes the updated contents back. This panic means the fs::write failed — typically a permissions problem, a read-only filesystem, or the target path being a directory. The version sync cannot complete, so the build aborts.

Solutions

  1. Check and fix file permissions: chmod u+w packages/pydantic-monty/pyproject.toml (or chown to your user)
  2. If the source tree is mounted read-only, copy the workspace to a writable location or run the build with the sources writable
  3. If you did not intend a version change, ensure CARGO_PKG_VERSION matches the pins already in pyproject.toml so no write is needed

Example fix

// before
$ cargo build  # after bumping version
panicked: failed to write packages/pydantic-monty/pyproject.toml: Permission denied

// after
chmod u+w packages/pydantic-monty/pyproject.toml
cargo build  # version synced, succeeds
Defensive patterns

Strategy: try-catch

Validate before calling

[ -w packages/pydantic-monty/pyproject.toml ] && echo writable || echo 'not writable'

Prevention

When it happens

Trigger: Cargo version changed (so `changed == true`) and the build script attempts to write packages/pydantic-monty/pyproject.toml while it is read-only, owned by another user, or lives on a read-only mount (e.g. packaged sources mounted ro in a container).

Common situations: Building inside a Docker container with the repo mounted read-only; bumping the workspace version locally when the file is owned by root; running a build as a non-privileged user in a shared checkout.

Understand the failure class

Background: "failed to write file", "Could not save figure", "Error saving remote file" — file write failed: causes and fixes across languages and libraries — this error's family across 38 libraries.

Related errors


AI-assisted analysis of pydantic/monty@adc986b362 (2026-09-13). Data as JSON: /api/errors/7a64d753c49c2461. Report an issue: GitHub.

Appendix: source

Thrown at crates/monty-python/build.rs:95

    // which would otherwise leave the metapackage frozen at an old release.
    for (dist, found) in PINNED_DISTS.iter().zip(pins) {
        assert_eq!(
            found,
            1,
            "expected exactly one `{dist}` pin in [project].dependencies of {}, found {found}",
            path.display()
        );
    }
    assert_eq!(
        versions,
        1,
        "expected exactly one `version = \"...\"` line in {}, found {versions}",
        path.display()
    );

    if changed {
        eprintln!("Updating {} to {pin_version}", path.display());
        fs::write(path, &result).unwrap_or_else(|e| panic!("failed to write {}: {e}", path.display()));
    }
}

/// The PyPI distributions the metapackage pins exactly; see [`sync_meta_pyproject`].
const PINNED_DISTS: [&str; 2] = ["pydantic-monty-client", "pydantic-monty-runtime"];

/// The metapackage's pyproject.toml, which owns the `pydantic-monty` name on
/// PyPI, or `None` when there is nothing to sync.
///
/// The metapackage has no Cargo package of its own to carry the version, so
/// this build script — the one belonging to the distribution it pins —
/// maintains it. It lives outside this crate, and maturin's sdist ships only
/// the crate and its Rust path dependencies, so a source install legitimately
/// has no metapackage and must not panic over that.
fn meta_pyproject_path() -> Option<PathBuf> {
    let path = PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set"))
        .join("../../packages/pydantic-monty/pyproject.toml");
    if path.is_file() {

View on GitHub (pinned to adc986b362)