Strange behaviour when organizing from an empty string
Metadata
Current evaluation
Fixed by merged PR #1532 in commit 46eeb3e8, which handles empty-string organize paths as a no-op when source and destination resolve to the same directory.
Resolution: closed resolved
Reason: PR #1532 was merged as commit 46eeb3e8. It fixes empty-string organize handling and adds regression coverage.
Evaluated by: mr-cal-bot
No scores available.
Issue body
### Bug Description
Files or directories seem to disappear if an organize key is an empty string.
I can't find the pattern by which the file disappears, but pretty consistently there is at least one missing file/directory. They either don't make it into the install directory or are removed during the organize process.
### To Reproduce
Consider the part YAML below. When used as-is, the expected behaviour occurs:
```
(craft-parts) lengau@ratel:~/Work/Code/craft-parts$ python -m craft_parts prime
INFO:craft_parts.executor.executor:Installing build-packages
Execute: Pull a
Execute: Overlay a
Execute: Build a
Execute: Stage a
Execute: Prime a
(craft-parts) lengau@ratel:~/Work/Code/craft-parts$ tree prime
prime
├── my-dir
│ └── subdir-a
│ └── file
└── my-file
3 directories, 2 files
```
When organizing `'*'` to `''`, it works as expected as well:
```
(craft-parts) lengau@ratel:~/Work/Code/craft-parts$ python -m craft_parts clean
Clean all parts.
(craft-parts) lengau@ratel:~/Work/Code/craft-parts$ python -m craft_parts prime
INFO:craft_parts.executor.executor:Installing build-packages
Execute: Pull a
Execute: Overlay a
Execute: Build a
Execute: Stage a
Execute: Prime a
(craft-parts) lengau@ratel:~/Work/Code/craft-parts$ tree prime
prime
├── my-dir
│ └── subdir-a
│ └── file
└── my-file
3 directories, 2 files
```
However, when organizing from an empty string, a file goes missing:
```
(craft-parts) lengau@ratel:~/Work/Code/craft-parts$ python -m craft_parts clean
Clean all parts.
(craft-parts) lengau@ratel:~/Work/Code/craft-parts$ python -m craft_parts prime
INFO:craft_parts.executor.executor:Installing build-packages
Execute: Pull a
Execute: Overlay a
Execute: Build a
Error: Failed to copy '/home/lengau/Work/Code/craft-parts/parts/a/install/my-file': no such file or directory.
(craft-parts) lengau@ratel:~/Work/Code/craft-parts$ tree parts/a/install/
parts/a/install/
└── my-dir
└── subdir-a
└── file
3 directories, 1 file
```
If you comment out the creation of `my-file`, then `my-dir/subdir-a/file` goes missing instead:
```
(craft-parts) lengau@ratel:~/Work/Code/craft-parts$ python -m craft_parts clean
Clean all parts.
(craft-parts) lengau@ratel:~/Work/Code/craft-parts$ python -m craft_parts prime
INFO:craft_parts.executor.executor:Installing build-packages
Execute: Pull a
Execute: Overlay a
Execute: Build a
Error: Failed to copy '/home/lengau/Work/Code/craft-parts/parts/a/install/my-dir/subdir-a/file': no such file or directory.
(craft-parts) lengau@ratel:~/Work/Code/craft-parts$ tree parts/a/install/
parts/a/install/
└── my-dir
└── subdir-a
3 directories, 0 files
```
### part yaml
```shell
parts:
a:
plugin: nil
override-build: |
mkdir -p "${CRAFT_PART_INSTALL}/my-dir/subdir-a"
touch "${CRAFT_PART_INSTALL}/my-file"
touch "${CRAFT_PART_INSTALL}/my-dir/subdir-a/file"
# organize:
# '': ''
```
### Relevant log output
```shell
DEBUG:craft_parts.packages:get repository attribute: attr=configure, repository:<class 'craft_parts.packages.deb.Ubuntu'>
DEBUG:craft_parts.utils.os_utils:is_snap: False, SNAP_NAME set to None
DEBUG:craft_parts.sequencer:process a:1
DEBUG:craft_parts.sequencer:add action a:1(0)
DEBUG:craft_parts.sequencer:process a:2
DEBUG:craft_parts.sequencer:add action a:2(0)
DEBUG:craft_parts.sequencer:process a:3
DEBUG:craft_parts.sequencer:add action a:3(0)
DEBUG:craft_parts.sequencer:process a:4
DEBUG:craft_parts.sequencer:add action a:4(0)
DEBUG:craft_parts.sequencer:process a:5
DEBUG:craft_parts.sequencer:add action a:5(0)
INFO:craft_parts.executor.executor:Installing build-packages
DEBUG:craft_parts.packages:get repository attribute: attr=install_packages, repository:<class 'craft_parts.packages.deb.Ubuntu'>
DEBUG:craft_parts.executor.executor:verify plugin environment for part 'a'
DEBUG:craft_parts.packages:set repository attribute: attr=stage_packages_filters, value=None, repo:<class 'craft_parts.packages.deb.Ubuntu'>
Execute: Pull a
DEBUG:craft_parts.executor.executor:execute action a:Action(part_name='a', step=Step.PULL, action_type=ActionType.RUN, reason=None, project_vars=ProjectVarInfo(root={}), properties=ActionProperties(changed_files=None, changed_dirs=None))
Execute: Overlay a
DEBUG:craft_parts.executor.executor:execute action a:Action(part_name='a', step=Step.OVERLAY, action_type=ActionType.RUN, reason=None, project_vars=ProjectVarInfo(root={}), properties=ActionProperties(changed_files=None, changed_dirs=None))
Execute: Build a
DEBUG:craft_parts.executor.executor:execute action a:Action(part_name='a', step=Step.BUILD, action_type=ActionType.RUN, reason=None, project_vars=ProjectVarInfo(root={}), properties=ActionProperties(changed_files=None, changed_dirs=None))
DEBUG:craft_parts.state_manager.states:load state file: /home/lengau/Work/Code/craft-parts/parts/a/state/pull
DEBUG:craft_parts.packages:get repository attribute: attr=unpack_stage_packages, repository:<class 'craft_parts.packages.deb.Ubuntu'>
DEBUG:craft_parts.executor.part_handler:remove directory /home/lengau/Work/Code/craft-parts/parts/a/build
DEBUG:craft_parts.executor.step_handler:Executing PosixPath('/tmp/tmp___xqkav/scriptlet.sh')
DEBUG:craft_parts.executor.part_handler:Run pre-organize callbacks
Error: Failed to copy '/home/lengau/Work/Code/craft-parts/parts/a/install/my-file': no such file or directory.
```
Evaluation history
| Date | Model | Scores | Action | Summary |
|---|---|---|---|---|
| stealth/space-bunny-alpha | — | closed resolved | Fixed by merged PR #1532 in commit 46eeb3e8, which handles empty-string organize paths as a no-op when source and destination resolve to the same directory. | |
| stealth/space-bunny-alpha | — | closed resolved | Fixed in PR #1532 (commit 46eeb3e8) by correctly handling empty-string organize paths; regression coverage was added. | |
| qwen/qwen3.6-35b-a3b | — | — | Closed after a developer volunteered to fix the empty-string organize bug and add regression tests. An internal Jira ticket (CRAFT-5087) was created to track the resolution. | |
| qwen/qwen3.6-35b-a3b |
Staleness:
35
Complexity:
45
Confidence:
90
Support Request:
5
|
keep open | Bug where files disappear during the organize step when using an empty string as the source key. Triaged and acknowledged; a contributor has volunteered to reproduce it and add a regression test before fixing. | |
| qwen3.6-35b-a3b-mtp-q6 |
Staleness:
55
Complexity:
45
Confidence:
85
Support Request:
5
|
keep open | Bug where files disappear during the organize step when using an empty string key. Triaged and claimed by a contributor, but inactive for ~3 months awaiting reproduction and fix. |
Update history
| Date | Change |
|---|---|
| closed |
Related work
-
Likely Fixed By:
canonical/craft-parts#1532
(confidence 100%)
Merged fix for the empty-string organize path behavior reported in #1526.
Related issues
| Issue | Project | State | Summary | Similarity |
|---|---|---|---|---|
| #1532 fix(executor): handle empty-string organize path | craft-parts | merged | Merged #1532 in commit 46eeb3e8, making organize: {'': ''} a no-op when source and destination resolve to the same directory; #1526 was closed. | |
| #1544 fix: prevent organizing a directory into itself | craft-parts | closed | PR #1544 was closed unmerged as incomplete; the solution was replaced by the organize-from-build rework in #1598 (commit cc02958b). | |
| #784 Support globbing for organize | snapcraft | merged | Merged PR #784 added glob expansion for organize entries, resolving LP #1616459, in commit 260671d1a. Empty-parent-directory cleanup was deferred. | |
| #1370 fix: make sure the destination does not exist before organizing | craft-parts | merged | Merged PR #1370 as commit 7fe7c84e, removing any existing organize destination before moving files and fixing #1366/CRAFT-4870. |