CNSeniorious000 / CNSeniorious000/dsh-py-codeact
One cell can corrupt the MCP modules for every agent in the process, permanently and invisibly
- Dominant language
- JavaScript
- Stars
- 1
- Forks
- 0
- Avg merge
- 2h 47m
- Merged PRs (30d)
- 27
Description
`__dsh__.tools.mcp` and its server modules are process-global singletons. #9 added a `__setattr__` guard for exactly this, and `mcp_server_module` so a deleted server module comes back. Two holes remain, both reproduced with two live shells in one process at `3c0195f`.
### The `__setattr__` guard is bypassed by the inherited `ModuleType.__init__`
```python
# shell A
from __dsh__.tools import mcp
import types
types.ModuleType.__init__(mcp.gh, "x") # no attribute assignment, so no guard
sorted(dir(mcp.gh)) # -> []
```
```python
# shell B, a different agent
sorted(dir(mcp.gh)) # -> []
```
The server is blanked for the whole process. `__setattr__` refuses non-dunder writes, but `ModuleType.__init__` resets `__dict__` without going through it. Survives every rebind — `mcp_server_module` only rebuilds a module that is *missing* from `sys.modules`, and this one is still there.
### `del sys.modules["__dsh__.tools.mcp"]` kills the root process-wide
```python
# shell A
import sys; del sys.modules["__dsh__.tools.mcp"]
```
```python
# shell B
from __dsh__.tools.mcp import gh # -> ModuleNotFoundError: No module named '__dsh__.tools.mcp'
```
`mcp_server_module` is the single construction site so a deleted **server** module is rebuilt — confirmed working. The **root** is registered once at import with no equivalent, so nothing brings it back. `dir(mcp)` still answers and the deep import form still resolves, so one of the two import forms the README advertises is dead behind a namespace that looks healthy.
### Both are the same shape
Cross-agent, permanent, and invisible to the listing. A subagent's stray cell breaks its parent, and the parent's diagnosis (`dir(mcp)`) reports nothing wrong. The fix has to make the state recoverable rather than merely guarded — the guard was already there and the process still lost the module.
### Related, and the root cause of a third symptom
`sys.modules['__main__']` belongs to whichever shell was created LAST:
```
shell A: sys.modules['__main__'].__dict__ is globals() -> False
shell B: sys.modules['__main__'].__dict__ is globals() -> True
```
The kernel neutralises `sys.stdout`, `sys.displayhook` and `__dsh__.tools` against exactly this per-shell aliasing; `__main__` is not covered, and IPython's `init_sys_modules` re-points it per shell. Anything routed through `__main__` then breaks the README's "globals are not shared between shells" claim — `pickle` most visibly, both loudly (`PicklingError: Can't pickle `) and silently (a parent unpickling **its own** blob gets its data back inside a subagent's same-named class, no error).
Contributor guide
No contributing guide indexed for this repository
Research direction
Reproduce the two-shell examples first, then inspect the mcp_server_module construction site, the root __dsh__.tools.mcp registration, and IPython's init_sys_modules handling of __main__. Done means a ModuleType.__init__ reset and deletion of the root module recover safely, while globals and pickle behavior remain isolated as promised by the README.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- tooling
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100