Skip to content

BUG: Stop executeFile from leaving its script directory on sys.path#1413

Merged
jamesobutler merged 1 commit intocommontk:masterfrom
ebrahimebrahim:bug-executefile-syspath-leak
Apr 14, 2026
Merged

BUG: Stop executeFile from leaving its script directory on sys.path#1413
jamesobutler merged 1 commit intocommontk:masterfrom
ebrahimebrahim:bug-executefile-syspath-leak

Conversation

@ebrahimebrahim
Copy link
Copy Markdown
Contributor

Summary

ctkAbstractPythonManager::executeFile prepends the executed script's directory to sys.path so that the script can import its siblings, mirroring how CPython runs a top-level script. The current implementation never removes that entry, so in long-running CTK-based applications the directory stays on sys.path for the rest of the process, and each subsequent executeFile call adds another entry.

Why it matters

Once a directory is permanently on sys.path, every file inside it becomes importable as a top-level module — and silently shadows any PyPI package with the same name. The failure mode is invisible until something happens to collide, at which point imports start resolving to the wrong file with no warning.

This was discovered while working on 3D Slicer PR #9010. Slicer runs its startup script slicerqt.py via executeFile, and that script used to live inside the slicer/ package directory — so slicer/ was permanently on sys.path, and any new file added to that package (for example slicer/packaging.py) would have shadowed the corresponding third-party PyPI package. The Slicer PR worked around the symptom by relocating slicerqt.py, but every CTK consumer is exposed to the same problem, so the right fix lives here.

The fix

Wrap the sys.path.insert(0, ...) and the exec() of the user script in a Python try/finally block. The directory is added before the script runs (preserving today's behaviour for the script itself) and is removed once the script finishes, regardless of whether it raised. The cleanup uses sys.path.remove(value) rather than pop(0) so that entries the script itself added cannot corrupt the cleanup, and it tolerates the case where the script already removed the entry.

Tests

Adds a regression test testExecuteFileDoesNotLeakSysPath to ctkAbstractPythonManagerTest that calls executeFile and verifies sys.path is unchanged afterwards. The test was confirmed to fail on the unfixed code and pass with this patch applied.

Related

ctkAbstractPythonManager::executeFile prepended the script's directory
to sys.path so the script could import siblings, but never removed it,
so the entry accumulated on sys.path for the rest of the process.

Wrap the sys.path.insert and exec() in a Python try/finally and add a
regression test.

Discovered via 3D Slicer PR Slicer/Slicer#9010.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Copy link
Copy Markdown
Member

@pieper pieper left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me 👍

At some point we should discuss relocating the inlined python into a proper python package to minimize the noise in the C++ code, but that's outside of the scope here.

@ebrahimebrahim ebrahimebrahim marked this pull request as ready for review April 14, 2026 13:12
@ebrahimebrahim
Copy link
Copy Markdown
Contributor Author

Thanks for looking at this!

@ebrahimebrahim
Copy link
Copy Markdown
Contributor Author

(I don't have permission to merge this so someone else will have to, if it looks good)

@jamesobutler jamesobutler merged commit 7cb6325 into commontk:master Apr 14, 2026
4 checks passed
@jamesobutler
Copy link
Copy Markdown
Contributor

@ebrahimebrahim For this to become used by Slicer, will need to finalize the upgrade that was started in Slicer/Slicer#9097.

@ebrahimebrahim
Copy link
Copy Markdown
Contributor Author

Thanks for merging

(There is no rush to use this by Slicer since we work around it in Slicer/Slicer#9010 for example -- I just felt that it would be good to fix it here so that one else runs into it. So no rush)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants