astral-sh/ruff · error

System should be writable

Error message

System should be writable

What it means

fix_all in ty_python_semantic/src/fixes.rs mutates source files, so it requires the database's System to implement WritableSystem. System::as_writable (ruff_db/src/system.rs:221) returns None for read-only systems and the expect panics. RealSystem is always writable, so this panic is an API-embedding error (custom read-only System), not something the ty CLI itself produces on a normal filesystem.

Source

Thrown at crates/ty_python_semantic/src/fixes.rs:92

const MAX_ITERATIONS: usize = 10;

/// Applies all fixes for the given fix mode.
///
/// `check_file` is a separate parameter so that tests can easily mock out a file's diagnostics.
fn fix_all<F>(
    db: &mut dyn Db,
    mut diagnostics: Vec<Diagnostic>,
    fix_mode: FixMode,
    cancellation_token: &CancellationToken,
    check_file: F,
) -> Result<FixAllResults, Canceled>
where
    F: Fn(&dyn Db, File) -> Vec<Diagnostic> + Sync,
{
    let system = WritableSystem::dyn_clone(
        db.system()
            .as_writable()
            .expect("System should be writable"),
    );

    let has_fixable = diagnostics
        .iter()
        .any(|diagnostic| fix_mode.is_fixable(diagnostic));

    // Early return if there are no diagnostics that can be suppressed to avoid all the heavy work below.
    if !has_fixable {
        return Ok(FixAllResults {
            diagnostics,
            count: 0,
        });
    }

    let mut by_file: BTreeMap<File, Vec<_>> = BTreeMap::new();

    // Group the diagnostics by file, leave the file-agnostic diagnostics in `diagnostics`.
    for diagnostic in diagnostics.extract_if(.., |diagnostic| diagnostic.primary_span().is_some()) {

View on GitHub (pinned to d1087a4b9e)

Solutions

  1. Construct the db with a writable system (RealSystem, or a TestSystem that implements WritableSystem) before calling fix-all
  2. If you implement System yourself, implement as_writable() to return Some along with the WritableSystem methods (write_file, create_new_file, ...)
  3. Gate the call: skip fix-all and return check-only results when db.system().as_writable().is_none()

Example fix

// before
let results = fix_all(&mut db, diagnostics, fix_mode, &token, check_file)?;

// after
if db.system().as_writable().is_none() {
    return Ok(FixAllResults { diagnostics, count: 0 });
}
let results = fix_all(&mut db, diagnostics, fix_mode, &token, check_file)?;
Defensive patterns

Strategy: validation

Validate before calling

if db.system().as_writable().is_none() {
    return Ok(FixAllResults { diagnostics, count: 0 }); // check-only fallback
}
let results = fix_all(&mut db, diagnostics, fix_mode, &token, check_file)?;

Prevention

When it happens

Trigger: Invoking the fix-all path (`ty check --fix`, LSP fix-all) with a db whose system is a custom read-only System implementation that returns None from as_writable, e.g., an in-memory or virtual filesystem in a test harness or editor integration.

Common situations: Editor integrations and CI harnesses that build ty's Db with a sandboxed or virtual System but call the fix API; tests that reuse a read-only test system for fix-all cases.

Related errors


AI-assisted analysis of astral-sh/ruff@d1087a4b9e (2026-08-20). Data as JSON: /api/errors/913f08beaf25a058. Report an issue: GitHub.