feat(examples): add interactive Claude Code box example#209
feat(examples): add interactive Claude Code box example#209DorianZheng merged 5 commits intomainfrom
Conversation
Provide a persistent interactive terminal example for installing Claude Code and document it in the examples README. Co-authored-by: Cursor <[email protected]>
There was a problem hiding this comment.
Pull request overview
This PR adds a new interactive example for installing Claude Code in a persistent containerized environment. The example demonstrates how to create and reuse boxes across sessions, allowing users to interactively install and configure Claude Code within a container.
Changes:
- Adds interactive_claude_ubuntu_example.py showing persistent box usage with manual Claude Code installation
- Implements PTY-based interactive shell with manual I/O forwarding for reattached boxes
- Updates Python examples README to document the new example
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 10 comments.
| File | Description |
|---|---|
| examples/python/interactive_claude_ubuntu_example.py | New example demonstrating persistent box creation, interactive shell sessions, and box reattachment patterns |
| examples/python/README.md | Documents the new interactive Claude Code example in the examples list and adds detailed description |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -0,0 +1,207 @@ | |||
| #!/usr/bin/env python3 | |||
There was a problem hiding this comment.
The filename contains "ubuntu" but the script actually uses "debian:bookworm-slim" by default (line 27). Consider renaming the file to "interactive_claude_debian_example.py" or "interactive_claude_example.py" to match the actual default behavior, or change the default image to Ubuntu to match the filename.
| if BOX_ID: | ||
| box = await runtime.get(BOX_ID) | ||
| if box is None: | ||
| raise RuntimeError(f"Box not found: {BOX_ID}") | ||
| print(f"Reattached to box id: {BOX_ID}") | ||
| await run_interactive_shell(box, env, shell="/bin/bash") |
There was a problem hiding this comment.
When reattaching to an existing box by BOX_ID (line 163), the code passes the 'env' variable to run_interactive_shell, but this 'env' is constructed fresh each time (lines 145-148). However, when calling runtime.get(BOX_ID), the box may have been created with different environment variables. Consider whether the environment variables should be reapplied to the box when reattaching, or document that the env parameter here only affects the shell session, not the box's persistent environment.
| existing = await runtime.get(BOX_NAME) | ||
| if existing is not None: | ||
| print("Found existing box with same name.") | ||
| print(f"Set BOXLITE_CLAUDE_BOX_ID={existing.id} to reattach.") | ||
| print("Or remove it and re-run to create a new one.") | ||
| await run_interactive_shell(existing, env, shell="/bin/bash") | ||
| await existing.stop() | ||
| print("Box stopped (data persisted). Re-run to continue.") | ||
| return |
There was a problem hiding this comment.
When an existing box is found by name (line 172), the code uses the freshly created BoxOptions (lines 154-160) but never applies them to the existing box. This means the memory_mib, disk_size_gb, and env settings defined in the options won't be used. The existing box will retain its original configuration. Either remove the unused options construction or clarify in the code comments that these settings only apply to new boxes.
| ### interactive_claude_ubuntu_example.py | ||
| Interactive terminal for Claude Code: | ||
| - Persistent box with a bash shell | ||
| - Install Claude Code directly in the terminal | ||
| - Reuse the same box across sessions | ||
|
|
There was a problem hiding this comment.
The documentation refers to "Claude Code" but the filename says "ubuntu" while the actual default image is Debian. Consider updating the description to clarify the actual default OS or make it OS-agnostic (e.g., "Interactive terminal for Claude Code on Linux").
| async with InteractiveBox( | ||
| image=IMAGE, | ||
| shell="/bin/bash", | ||
| name=BOX_NAME, | ||
| auto_remove=False, | ||
| env=env, | ||
| memory_mib=2048, | ||
| disk_size_gb=8, | ||
| ) as itbox: |
There was a problem hiding this comment.
The configuration parameters (image, memory_mib, disk_size_gb, auto_remove, env) are duplicated between the unused BoxOptions (lines 154-160) and the InteractiveBox creation (lines 183-191). This violates the DRY principle and creates a maintenance burden. If you keep the BoxOptions, pass them to InteractiveBox; otherwise, remove the unused BoxOptions entirely.
| print() | ||
|
|
||
| try: | ||
| import boxlite |
There was a problem hiding this comment.
Module 'boxlite' is imported with both 'import' and 'import from'.
|
@copilot open a new pull request to apply changes based on the comments in this thread |
|
@yingjunwu I've opened a new pull request, #210, to work on those changes. Once the pull request is ready, I'll request review from you. |
Co-authored-by: Copilot <[email protected]>
Co-authored-by: Copilot <[email protected]>
Co-authored-by: Copilot <[email protected]>
Co-authored-by: Copilot <[email protected]>
Summary
Test plan
Made with Cursor