fix(mirage-sandbox): don't re-home imported functions onto the sandbox namespace - #94
Merged
Merged
Conversation
…x namespace Re-homing restored functions onto the live namespace (#92) matched *any* function whose globals were not `ns`, including ones imported from a module. An imported function closes over its module's globals and reaches names in them at call time, so rebuilding it on `ns` strips those names: from collections import Counter # run 1 Counter("aa") # run 2 NameError: name '_collections_abc' is not defined `Counter.update` reads `_collections_abc` and `_count_elements` from `collections`' own globals; `os.path.join` reads `sep` from `posixpath`'s. Because classes are re-homed in place via `setattr`, the damage outlived the name that triggered it — a later plain `import collections` inherited the broken `Counter`. Re-home only functions defined in the sandbox. Their (ghost) globals are a copy of `ns`, which carries `__name__ == "__main__"`; an imported function carries its own module name. The cross-run cases #92 fixed are unaffected and still covered. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug
MirageSandboxpersists its namespace betweenruncalls with dill, then re-homes restored functions onto the livensso they share one global namespace (#92). The predicate matched any function whose__globals__was notns— including functions imported from a module:An imported function closes over its own module's globals and reaches names in them at call time.
Counter.updatereads_collections_abcand_count_elementsfromcollections' globals;os.path.joinreadssepfromposixpath's. Rebuilding them onnsstrips exactly those.Because classes are re-homed in place (
setattron the class object), the damage outlived the name that triggered it: after any run importedCounter, a later plainimport collectionsinherited the broken class.Found in a long agent run, where it accounted for 264 sandbox failures across four tasks —
_collections_abc(122),sep(62),_itemgetter(48),_count_elements(32). It only bites from the second run onward, which is what makes it confusing in the wild: the import turn works, and the code that uses the import fails.The fix
Re-home only functions defined in the sandbox. Their ghost globals are a copy of
ns, which carries__name__ == "__main__"; an imported function carries its own module name. One extra clause on the existing predicate.Tests
Two colocated tests, both failing on
mainand passing here:test_imported_function_keeps_its_own_module_globals—os.path.joinstill works a run after it was imported.test_imported_class_methods_keep_their_module_globals—Counterworks a run after import, and a subsequent plainimport collectionsis undamaged.The cross-run cases #92 added (
test_function_sees_names_defined_in_later_runs,test_method_sees_names_defined_in_later_runs,test_rehomed_function_keeps_closure_and_kwdefaults) still pass, so this narrows the predicate without giving back what #92 bought.Full sandbox suite: 121 passed, 9 skipped.
🤖 Generated with Claude Code