← Back to issue list

A part's install directory doesn't get cleaned if the prior build failed.

View original Github issue

Metadata

Project
craft-parts
Number
#1780
Type
issue
State
open
Author
lengau
Labels
โ€”
Created
Updated
Closed
โ€”

Current evaluation

Install directory not cleaned after failed build, causing subsequent builds to fail with unclear errors. New issue with no labels or maintainer response.

Suggested action: needs triage

Reason: This is a new, unlabelled issue with no maintainer response or comments. The issue is well-scoped with a clear reproducer test pointing to the exact code location in part_handler.py where the build directory is cleaned but the install directory is not. The bug is confirmed by examining the code: _run_build removes part_build_dir but not part_install_dir on failure, unlike _update_build which does clean the install directory.

Evaluated by: mr-cal-bot

Impact: 60 Quick Win: 38 Complexity: 20 Confidence: 80 Actionability: 80

Issue body

### Bug Description This is a distillation of one part of https://github.com/canonical/charmcraft/issues/2969 When the previous build of a part failed, we [remove the build directory](https://github.com/canonical/craft-parts/blob/cd1265cdd3347ce1328ddf0971df88e1d1b89327/craft_parts/executor/part_handler.py#L470-L471), but we don't remove the install directory. If the build plugin isn't idempotent (or the user provides a non-idempotent `override-build` script), this can result in a single failed build making all future builds fail for reasons that are unclear to the user. ### To Reproduce I got Gemini to create this xfailing test that we can put in `TestPartHandling`: ```python @pytest.mark.xfail( raises=AssertionError, reason="https://github.com/canonical/charmcraft/issues/2969", strict=True, ) def test_run_build_cleans_install_dir_after_failure( self, mocker: MockerFixture ) -> None: mocker.patch( "craft_parts.packages.Repository.get_installed_packages", return_value=[], ) mocker.patch( "craft_parts.packages.snaps.get_installed_snaps", return_value=[], ) mocker.patch("subprocess.check_output", return_value=b"os-info") leftover_file = self._part.part_install_dir / "leftover.txt" def _failing_build(*args: Any, **kwargs: Any) -> None: leftover_file.parent.mkdir(parents=True, exist_ok=True) leftover_file.write_text("partial content") raise errors.ScriptletRunError( part_name="foo", scriptlet_name="override-build", exit_code=1 ) mocker.patch( "craft_parts.executor.step_handler.StepHandler._builtin_build", side_effect=_failing_build, ) with pytest.raises(errors.ScriptletRunError): self._handler.run_action( Action(self._part.name, Step.BUILD, action_type=ActionType.RUN) ) # TODO: Change this back to an assert. # This raises a RuntimeError to ensure that the caught AssertionError is # the correct one. if not leftover_file.exists(): raise RuntimeError( "Precondition failed: failed build did not leave file in install dir" ) mocker.patch( "craft_parts.executor.step_handler.StepHandler._builtin_build", return_value=None, ) self._handler.run_action( Action(self._part.name, Step.BUILD, action_type=ActionType.RUN) ) assert not leftover_file.exists() ### part yaml ```shell ``` ### Relevant log output ```shell Relevant section from charmcraft#2969: :: Removing python symlinks in /root/parts/charm-poetry/install/venv/bin :: + shopt -s extglob :: + rm -rf /root/parts/charm-poetry/install/venv/bin/Activate.ps1 /root/parts/charm-poetry/install/venv/bin/activate.csh /root/parts/charm-poetry/install/venv/bin/activate.fish /root/parts/charm-poetry/install/venv/bin/๐œ‹thon :: + shopt -u extglob :: + sed -i 's#^VIRTUAL_ENV=.*$#VIRTUAL_ENV="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )/.." \&> /dev/null \&\& pwd )"#' /root/parts/charm-poetry/install/venv/bin/activate :: + '[' -L /root/parts/charm-poetry/install/venv/lib64 ']' :: + rm -f /root/parts/charm-poetry/install/venv/lib64 Building files :: + craftctl default :: + cp --archive --link --no-dereference . /root/parts/files/install :: + exit 1 'override-build' in part 'files' failed with code 1. Detailed information: :: + craftctl default :: + exit 1 Recommended resolution: Review the scriptlet and make sure it's correct. For more information, check out: https://documentation.ubuntu.com/charmcraft/4/reference/plugins/ Failed to run charmcraft in instance Full execution log: '/home/ubuntu/.local/state/charmcraft/log/charmcraft-20261022-111102.907563.log' ubuntu@dualla:~/bug-report-charmcraft-monorepo-repack/kubernetes$ CHARMCRAFT_EXPERIMENTAL_MONOREPO=1 charmcraft pack -v Starting charmcraft, version 4.5.0 Logging execution to '/home/ubuntu/.local/state/charmcraft/log/charmcraft-20261022-111342.742597.log' Launching managed ubuntu 26.04 instance... Starting instance Starting charmcraft, version 4.5.0 Logging execution to '/tmp/charmcraft.log' Initializing lifecycle Installing build-packages Skipping pull for charm-poetry (already ran) Skipping pull for files (already ran) Skipping build for charm-poetry (already ran) Building files :: + craftctl default :: + cp --archive --link --no-dereference . /root/parts/files/install :: cp: cannot create hard link '/root/parts/files/install/./pyproject.toml' to './pyproject.toml': File exists :: cp: cannot create hard link '/root/parts/files/install/./poetry.lock' to './poetry.lock': File exists :: cp: cannot create hard link '/root/parts/files/install/./metadata.yaml' to './metadata.yaml': File exists :: cp: cannot create hard link '/root/parts/files/install/./charmcraft.yaml' to './charmcraft.yaml': File exists Failed to run the build script for part 'files'. Detailed information: :: + cp --archive --link --no-dereference . /root/parts/files/install :: cp: cannot create hard link '/root/parts/files/install/./pyproject.toml' to './pyproject.toml': File exists :: cp: cannot create hard link '/root/parts/files/install/./poetry.lock' to './poetry.lock': File exists :: cp: cannot create hard link '/root/parts/files/install/./metadata.yaml' to './metadata.yaml': File exists :: cp: cannot create hard link '/root/parts/files/install/./charmcraft.yaml' to './charmcraft.yaml': File exists Recommended resolution: Check the build output and verify the project can work with the 'dump' plugin. ```

Evaluation history

Date Model Scores Action Summary
qwen/qwen3.6-35b-a3b
Impact: 60
Quick Win: 38
Complexity: 20
Confidence: 80
Actionability: 80
needs triage Install directory not cleaned after failed build, causing subsequent builds to fail with unclear errors. New issue with no labels or maintainer response.
qwen/qwen3.6-35b-a3b
Impact: 35
Quick Win: 22
Complexity: 25
Confidence: 85
Actionability: 85
keep open Build directory is cleaned on failure but install directory is not, causing subsequent builds to fail with leftover partial state from non-idempotent builds.

Update history

Date Change
updated
created

Related work

  • Related To: canonical/charmcraft#2969 (confidence 95%)

    The issue body explicitly states this is a distillation of one part of charmcraft#2969, which contains the original bug report and log evidence.

Related issues

No related issues found above the similarity threshold.