From da18ff551416c6c8acb54909894edaeab6b7e3be Mon Sep 17 00:00:00 2001 From: Amir Vakili Date: Thu, 20 Aug 2026 07:11:58 -0700 Subject: [PATCH] fix: open files read-only during format --check format --check only reads files to compare content but opened them with r+, which requires write permission and fails on read-only filesystems in certain CI setups. Use r for check mode and keep r+ for normal format runs that write back to disk. Signed-off-by: Amir Vakili --- sqlmesh/core/context.py | 3 ++- tests/core/test_format.py | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/sqlmesh/core/context.py b/sqlmesh/core/context.py index c3abff1d94..2167211a9e 100644 --- a/sqlmesh/core/context.py +++ b/sqlmesh/core/context.py @@ -1270,7 +1270,8 @@ def format( ): # introduced to satisfy type checker as still want to pull filter out as many targets as possible before loop continue - with open(target._path, "r+", encoding="utf-8") as file: + mode = "r" if check else "r+" + with open(target._path, mode, encoding="utf-8") as file: before = file.read() after = self._format( diff --git a/tests/core/test_format.py b/tests/core/test_format.py index 5a44e1b381..e5b2e8460b 100644 --- a/tests/core/test_format.py +++ b/tests/core/test_format.py @@ -1,4 +1,6 @@ +import os import pathlib +import stat from pytest_mock.plugin import MockerFixture from sqlmesh.core.config import Config @@ -146,6 +148,25 @@ def test_ignore_formating_files(tmp_path: pathlib.Path): ) +def test_format_check_read_only_files(tmp_path: pathlib.Path, mocker: MockerFixture): + models_dir = pathlib.Path("models") + + model_text = "MODEL(name this.model, dialect 'duckdb'); SELECT 1 AS col" + model = create_temp_file( + tmp_path, + pathlib.Path(models_dir, "model.sql"), + model_text, + ) + os.chmod(model, stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH) + + context = Context(paths=tmp_path, config=Config()) + context.console = mocker.Mock() + context.load() + + assert not context.format(check=True) + assert model.read_text(encoding="utf-8") == model_text + + def test_format_without_state_load(tmp_path: pathlib.Path, mocker: MockerFixture): mock = mocker.patch( "sqlmesh.core.state_sync.db.facade.EngineAdapterStateSync.get_versions",