Migrate checkpoints to per-commit granularity with API proxy transcript capture - #517
Conversation
Add support for creating one checkpoint per commit instead of one per push: - Add get_commits_in_push() helper to enumerate commits between old/new SHAs - Add capture_and_store_checkpoints_for_push() for multi-commit checkpoint creation - Modify gateway push handler to get remote ref before push and iterate commits - Each checkpoint now has push_sha pointing to the tip commit for traceability - Graceful fallback: if rev-list fails, creates single checkpoint for tip commit This is Phase 1 of issue #509 (per-commit checkpoints + API proxy capture). Transcript source is still Claude Code JSONL (to be migrated in Phase 3). Authored-by: egg
Add instrumentation to the Anthropic API proxy to capture request/response pairs for checkpoint creation: - New TranscriptBuffer class manages per-session buffer files in /tmp/egg-transcripts/ - Fixed 10MB buffer rotation drops oldest entries when size limit exceeded - Non-streaming responses captured with full content and usage - Streaming SSE responses reassembled to extract content blocks - Buffer entry schema documented with timestamps, model, messages, tools, usage - Thread-safe file writes with file locking - Global buffer cache for efficient access by container ID This is Phase 2 of issue #509 (per-commit checkpoints + API proxy capture). Phase 3 will migrate checkpoint extraction to use this buffer instead of Claude Code JSONL files. Authored-by: egg
Replace Claude Code JSONL extraction with API proxy buffer reading: - Add extract_transcript_from_proxy_buffer() and related extraction functions - Update CheckpointHandler.capture_checkpoint() to use proxy buffer instead of JSONL - Remove CLAUDE_PROJECTS_DIR constant (no longer needed) - Maintain same return types for backward compatibility The proxy buffer provides a stable API-based source for transcripts, eliminating dependency on Claude Code's internal file format which can change without notice. This is Phase 3 of issue #509. Phase 4 will clean up deprecated JSONL code and add buffer cleanup on session end. Authored-by: egg
- Remove all Claude Code JSONL-specific code from transcript_extractor.py (file now only contains proxy buffer extraction functions) - Add buffer cleanup on session end in session_manager.py - Update CLI help text to describe per-commit checkpoint behavior - Add architecture documentation in checkpoint_handler.py explaining the transcript flow from API proxy to checkpoint storage Authored-by: egg
There was a problem hiding this comment.
Review: PR #517 - Migrate checkpoints to per-commit granularity with API proxy transcript capture
This is a significant architectural change with several issues that need to be addressed before merging.
Critical Issues
1. Race condition in buffer rotation (gateway/transcript_buffer.py:310-369)
The _rotate_buffer() method reads the entire buffer file, truncates, and rewrites it. While there's a thread lock (self._lock), the file locking in _append_entry() uses fcntl.flock() which only locks during the write, not during rotation. This creates a TOCTOU race between rotation check and file operations:
def _maybe_rotate(self) -> None:
if not self.buffer_path.exists(): # Check
return
current_size = self.buffer_path.stat().st_size # Check
if current_size >= self._max_size:
self._rotate_buffer() # Act - reads file, truncates, writes
def _append_entry(self, entry_bytes: bytes) -> None:
with open(self.buffer_path, "ab") as f:
fcntl.flock(f.fileno(), fcntl.LOCK_EX) # Only locks this write
f.write(entry_bytes) # Can interleave with rotationIf another process writes between rotation's read and write, data will be lost. Fix: Hold the exclusive lock during the entire rotation operation, or use atomic file replacement.
2. Memory accumulation for streaming responses (gateway/gateway.py:2980-2996)
All streaming chunks are accumulated in memory before capture:
collected_chunks: list[bytes] = []
def generate() -> Any:
try:
for chunk in upstream.iter_bytes():
collected_chunks.append(chunk) # Accumulates all chunks
yield chunk
finally:
_capture_streaming_response(..., chunks=collected_chunks, ...)For long-running streaming responses with large content, this could consume significant memory. The proxy should work with a memory budget since it handles many concurrent requests. Consider capping the collected size and truncating if exceeded.
3. Missing error handling for failed API responses (gateway/gateway.py:2738-2779)
_capture_non_streaming_response() only captures when the response body is valid JSON. For 4xx/5xx errors or malformed responses, nothing is captured:
try:
response_json = json.loads(response_body)
except (json.JSONDecodeError, TypeError):
logger.debug("Could not parse response body for transcript capture")
return # Silently drops capture for all non-JSON responsesThis loses visibility into failed API calls, which are often the most important for debugging. Consider capturing error responses with at least the status code and error type.
Correctness Issues
4. Incomplete SSE parsing (gateway/gateway.py:2833-2920)
The _parse_sse_response() function doesn't handle all Anthropic SSE event types correctly:
-
Missing
input_tokensfrom message_start: Themessage_startevent contains usage.input_tokens, but onlymessage_delta.usageis captured (which only has output_tokens). This meansinput_tokensmay be missing or zero for streaming responses. -
Error events not handled: The
errorevent type from the streaming API is not captured, meaning API errors during streaming are lost. -
Partial JSON parsing failure drops entire tool_use: If the accumulated
partial_jsonfails to parse (line 2917-2919), the input is silently set to{}, but the tool_use block is still included. This could lead to confusing transcripts where tools appear to be called with empty input.
5. Tool result matching is order-dependent (shared/egg_contracts/transcript_extractor.py:282-311)
The tool result matching in extract_tool_calls_from_proxy_buffer() assumes tool results appear in a later entry than the corresponding tool_use:
tool_use_map: dict[str, ToolCall] = {} # Populated from response content
for entry in entries:
# ... populate tool_use_map from response ...
# Then look for results in request messages
for msg in req_messages:
if block.get("type") == "tool_result":
tool_use_id = block.get("tool_use_id", "")
if tool_use_id in tool_use_map: # Must exist from earlier entryIf entries are processed out of order (e.g., due to concurrent writes), results won't match. The code should be resilient to entry ordering.
6. Unsafe path construction (shared/egg_contracts/transcript_extractor.py:433-435)
def get_proxy_buffer_path(container_id: str) -> Path:
"""Get the default proxy buffer path for a container ID."""
return Path("/tmp/egg-transcripts") / f"{container_id}.jsonl"No validation on container_id. A malicious or malformed container ID like ../../../etc/passwd could construct paths outside the intended directory. While this is defense-in-depth (container IDs come from session manager), it should still be validated.
Design Issues
7. Force push handling creates single checkpoint for entire history
get_commits_in_push() correctly falls back to [new_sha] for force pushes:
if result.returncode == 0 and result.stdout.strip():
commits = result.stdout.strip().split("\n")
return [c for c in commits if c]
# Force push falls back to new_sha only
return [new_sha]However, on force push, git rev-list old..new returns nothing because old_sha may not be an ancestor of new_sha. This means force pushes only create a single checkpoint for the tip, regardless of how many commits are being force-pushed. This may be intentional, but should be documented.
8. Shared transcript for all commits in push
All commits in a push get the same transcript since the proxy buffer is shared:
for commit_sha in commits:
checkpoint = handler.capture_checkpoint(...) # Same buffer for allThis means if the session made 3 commits, all 3 checkpoints will contain the full session transcript. While this is acknowledged in the design, it's a significant storage overhead for multi-commit pushes. Consider at minimum deduplicating the transcript content.
9. No integration test for end-to-end per-commit flow
The test plan mentions an integration test test_checkpoint_e2e.py, but this file is not in the PR. The existing tests mock the internal functions but don't verify the actual git-push-to-checkpoint flow works correctly.
Minor Issues
10. Import inside function (gateway/gateway.py:2755, 2790)
def _capture_non_streaming_response(...):
import time # Repeated importThe time module is already imported at module level in gateway.py. These local imports are unnecessary.
11. Buffer directory permissions not set
TranscriptBuffer.__init__ creates the directory but doesn't ensure secure permissions:
self._buffer_dir.mkdir(parents=True, exist_ok=True)/tmp/egg-transcripts will be world-readable by default. Since this contains API request/response data, it should have restricted permissions (e.g., mode=0o700).
12. Inconsistent container_id parameter
In extract_transcript_from_proxy_buffer(), if container_id is not provided, it's inferred from the filename:
if container_id is None:
container_id = buffer_path.stem # Filename without extensionBut in checkpoint_handler.py, it's always passed explicitly. The fallback logic should either be removed or documented as intentional.
Summary
The architecture is sound and the per-commit granularity is a good improvement. However, there are several correctness and robustness issues that should be fixed:
- Must fix: Race condition in buffer rotation (critical for data integrity)
- Must fix: Memory accumulation for streaming (resource exhaustion risk)
- Should fix: Missing input_tokens capture for streaming responses (affects token usage accuracy)
- Should fix: Path validation for container_id (defense in depth)
- Consider: Adding the mentioned integration test before merging
Request changes until at least items 1-2 are addressed.
— Authored by egg
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Fixes critical and correctness issues raised in review: 1. Race condition in buffer rotation (Critical #1): - Use exclusive file lock during entire rotation operation - Write to temp file then atomically replace with os.replace() - Clean up temp file on error 2. Memory accumulation for streaming (Critical #2): - Cap collected chunks at 10MB to prevent resource exhaustion - Log when capture is truncated due to size limit 3. Missing error handling for failed API responses (Critical #3): - Capture 4xx/5xx responses with status code and error message - Pass status_code to capture function for proper handling 4. Missing input_tokens from message_start (Correctness #4): - Extract input_tokens, cache_read_input_tokens from message_start - message_delta only contains output_tokens per SSE spec 5. Error events not handled in SSE parsing (Correctness #4): - Handle 'error' event type and capture as error content block - Add input_parse_error flag when tool_use JSON fails to parse 6. Unsafe path construction (Correctness #6): - Validate container_id against path traversal characters - Verify resolved path is within buffer directory Minor fixes: - Move `import time` to module level (Minor #10) - Set buffer directory permissions to 0o700 (Minor #11) Authored-by: egg
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Re-review: PR #517 - Address review feedback on transcript capture
This re-review examines commit f65a0cd which addresses feedback from the previous review at 0c7da209.
Verification of Previous Issues
✅ Issue #1 (Critical): Race condition in buffer rotation - FIXED
The fix correctly addresses the race condition:
- Rotation now holds exclusive file lock during the entire operation (
gateway/transcript_buffer.py:339-377) - Uses atomic
os.replace()to swap temp file with original - Temp file cleanup on error is properly handled
Minor note: The lock is released after os.replace() which is correct since the replacement is atomic. The file descriptor is now stale after replace, but the unlock is still valid.
✅ Issue #2 (Critical): Memory accumulation for streaming - FIXED
The fix caps collected chunks at 10MB (gateway/gateway.py:3031-3052):
MAX_CAPTURE_SIZE = 10 * 1024 * 1024is defined- Size tracking with
collected_sizevariable capture_truncatedflag prevents further accumulation- Appropriate debug logging when truncation occurs
✅ Issue #3: Missing error handling for failed API responses - FIXED
Non-streaming error responses are now captured (gateway/gateway.py:2760-2773):
- 4xx/5xx responses with non-JSON bodies are captured with status code and truncated message
- Structured error responses are captured as error content blocks (
gateway/gateway.py:2779-2789) status_codeparameter added to_capture_non_streaming_response()
✅ Issue #4a: Missing input_tokens from message_start - FIXED
Input tokens are now extracted from message_start event (gateway/gateway.py:2897-2908):
input_tokens,cache_read_input_tokens,cache_creation_input_tokenscaptured- Comments clarify that
message_deltaonly containsoutput_tokens
✅ Issue #4b: Error events not handled in SSE parsing - FIXED
Error events from streaming API are now captured (gateway/gateway.py:2910-2918):
errorevent type handled- Error info added as content block with type "error"
stop_reasonset to "error"
✅ Issue #6: Unsafe path construction - FIXED
Path traversal validation added (shared/egg_contracts/transcript_extractor.py:447-459):
- Rejects container_ids containing
/,\,.., or empty strings - Additional
resolve().relative_to()check ensures path stays within base directory - Raises
ValueErrorwith descriptive message
✅ Minor #10: Import inside function - FIXED
import time moved to module level (gateway/gateway.py:33).
✅ Minor #11: Buffer directory permissions - FIXED
Buffer directory now created with mode=0o700 (gateway/transcript_buffer.py:98), with additional chmod call to fix existing directories.
New Issues in the Fix Commit
1. Partial input_parse_error handling is incomplete (Minor)
In _parse_sse_response() (gateway/gateway.py:2963-2970), when JSON parsing fails for tool_use input:
except json.JSONDecodeError:
logger.debug(
"Failed to parse tool_use input JSON",
tool_id=block.get("id"),
)
block["input"] = {}
block["input_parse_error"] = TrueThe input_parse_error flag is added but never consumed by downstream code. The transcript will contain a tool_use block with empty input and this flag, but there's no handling in extract_tool_calls_from_proxy_buffer() to surface this to the checkpoint. Consider either:
- Adding a comment explaining this flag is for future debugging
- Or including the raw
partial_inputin an error field for debugging
This is minor since the transcript is still captured, just with potentially confusing empty input.
2. Rotation temp file not flushed before rename (Minor)
In _rotate_buffer() (gateway/transcript_buffer.py:367-374):
temp_path = self.buffer_path.with_suffix(".jsonl.tmp")
with open(temp_path, "w") as tmp:
for entry in entries[-keep_count:]:
tmp.write(entry + "\n")
# Atomic replace - still holding lock on original file
os.replace(temp_path, self.buffer_path)The temp file is not explicitly flushed (tmp.flush()) and synced (os.fsync(tmp.fileno())) before os.replace(). While Python's context manager will flush on close, the os.replace() happens after the with block closes, so this is actually fine. However, there's no fsync which means on system crash, the replacement could be incomplete on some filesystems. For a buffer in /tmp that's cleared on restart anyway, this is acceptable.
Remaining Unaddressed Issues from Previous Review
Issue #5: Tool result matching is order-dependent
This issue from the previous review was not addressed. In extract_tool_calls_from_proxy_buffer() (shared/egg_contracts/transcript_extractor.py:282-311), tool results are matched to tool_use blocks assuming sequential ordering of buffer entries. If entries are processed out of order, results won't match. However, since the buffer is append-only and read sequentially, this may be acceptable in practice.
Issue #7: Force push handling creates single checkpoint for entire history
This was acknowledged as possibly intentional but should be documented. Not addressed in this commit.
Issue #8: Shared transcript for all commits in push
Storage overhead for multi-commit pushes where all checkpoints get the same transcript. Not addressed in this commit (acknowledged as known limitation).
Issue #9: No integration test for end-to-end per-commit flow
Still no test_checkpoint_e2e.py in this PR.
Issue #12: Inconsistent container_id parameter fallback
The fallback logic in extract_transcript_from_proxy_buffer() where container_id can be inferred from filename stem was not addressed.
Summary
The critical and high-priority issues from the previous review have been properly addressed:
- ✅ Race condition in buffer rotation (Critical #1) - Fixed with proper locking and atomic replace
- ✅ Memory accumulation for streaming (Critical #2) - Fixed with 10MB cap
- ✅ Missing error handling for failed API responses (Critical #3) - Fixed
- ✅ Missing input_tokens from message_start (Correctness #4a) - Fixed
- ✅ Error events not handled in SSE parsing (Correctness #4b) - Fixed
- ✅ Unsafe path construction (Correctness #6) - Fixed with validation
- ✅ Minor issues #10 and #11 - Fixed
The remaining issues (#5, #7, #8, #9, #12) are either design decisions, documentation, or test coverage - acceptable to address in follow-up work.
No blocking issues remain. The fixes are sound and address the critical concerns from the previous review.
— Authored by egg
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
- Include truncated raw_partial_input in tool_use blocks when JSON parsing fails, for debugging incomplete streaming responses (re-review issue #1) - Add docstring notes documenting: - Tool result matching order-dependency (original issue #5) - Force push checkpoint behavior (original issue #7) - Shared transcript for multi-commit pushes (original issue #8) - container_id fallback from filename (original issue #12) - Fix import sorting in gateway.py (ruff auto-fix) Authored-by: egg
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Re-review: PR #517 - Address re-review feedback (commit edbcad0)
This commit addresses the outstanding items from the previous re-review. The changes are minor: documentation improvements and one debugging enhancement.
Verification of Previous Issues
✅ Re-review Issue #1: input_parse_error handling incomplete - ADDRESSED
The new code adds raw_partial_input field when JSON parsing fails (gateway/gateway.py:2974):
block["raw_partial_input"] = partial_input[:1000] if len(partial_input) > 1000 else partial_inputThis preserves the raw input (truncated to 1KB) for debugging incomplete streaming responses. The truncation logic is correct and prevents buffer bloat.
✅ Original Issue #5: Tool result order dependency - DOCUMENTED
Added clear docstring in transcript_extractor.py:218-223 explaining that order-dependency is expected because the buffer is append-only and read sequentially.
✅ Original Issue #7: Force push checkpoint behavior - DOCUMENTED
Added note in checkpoint_handler.py:126-131 explaining that force pushes intentionally create only a single checkpoint for the tip since they represent history rewrites.
✅ Original Issue #8: Shared transcript for multi-commit pushes - DOCUMENTED
Added note in checkpoint_handler.py:743-748 explaining that transcript sharing is intentional as it captures the full session reasoning.
✅ Original Issue #12: container_id fallback from filename - DOCUMENTED
Added explanation in transcript_extractor.py:379-383 that the filename-based fallback supports standalone testing and ad-hoc extraction.
Import Reordering
The import statements in gateway.py (lines 55-149) were reorganized alphabetically by ruff auto-fix. This is a no-op change that improves code consistency.
Minor Observations (Non-blocking)
-
raw_partial_input not consumed downstream: The new
raw_partial_inputfield is written to the buffer but not consumed byextract_tool_calls_from_proxy_buffer()or displayed anywhere. This is fine for debugging purposes - developers can inspect the raw buffer when investigating streaming issues. However, consider adding a log line when this field is present during extraction to surface the issue more visibly. -
Truncation threshold is arbitrary: The 1000-char limit is reasonable for debugging but could be made a constant for consistency with
MAX_CAPTURE_SIZE. Low priority.
Summary
All feedback from the previous re-review has been addressed appropriately:
- Documentation clarifies the design decisions for order-dependency, force push handling, shared transcripts, and container_id fallback
- The
raw_partial_inputfield provides useful debugging context for streaming parse failures
No blocking issues. The PR is ready for human review and merge.
— Authored by egg
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
- Add RAW_INPUT_TRUNCATE_SIZE constant for the 1000 char truncation threshold used when preserving raw tool input on parse failure - Add warning log in transcript_extractor when extracting tool calls with input_parse_error flag, surfacing streaming parse failures during checkpoint extraction Authored-by: egg
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Re-review: PR #517 - Address minor observations (commit fb0ea31)
This commit addresses the two minor observations from the previous review.
Verification of Previous Observations
✅ Observation #1: Truncation threshold constant - ADDRESSED
A RAW_INPUT_TRUNCATE_SIZE = 1000 constant is now defined at gateway/gateway.py:2675 with a clear docstring explaining its purpose.
✅ Observation #2: raw_partial_input logging - ADDRESSED
Warning log added at transcript_extractor.py:250-258 that surfaces streaming parse failures during checkpoint extraction:
if block.get("input_parse_error"):
raw_input = block.get("raw_partial_input", "")
logger.warning(
"Tool call has incomplete input due to streaming parse failure: "
"tool=%s id=%s raw_input_preview=%s",
tool_name,
tool_use_id,
raw_input[:100] + "..." if len(raw_input) > 100 else raw_input,
)The implementation correctly:
- Uses
block.get("raw_partial_input", "")with a default empty string - Truncates the preview to 100 chars for log readability
- Uses standard Python logging format strings for lazy evaluation
New Observations (Non-blocking)
1. No test coverage for input_parse_error path
Neither test_anthropic_proxy.py nor test_transcript_extractor.py contain tests for the input_parse_error / raw_partial_input handling path. While the logic is simple and correct, adding a test would:
- Document the expected behavior for incomplete streaming responses
- Prevent regressions if this code path is modified
This is acceptable to address in follow-up work since the code path is defensive and rarely triggered.
Summary
The two minor observations from the previous review have been properly addressed:
- ✅ Constant defined for truncation threshold
- ✅ Warning log surfaces parse failures during extraction
No blocking issues. The PR is ready for human review and merge.
— Authored by egg
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Adds tests for the input_parse_error and raw_partial_input handling that was added for incomplete streaming JSON responses. This addresses the non-blocking observation from the PR review to ensure the defensive code path is documented through tests and protected from regressions. - TestParseSSEResponse: Tests that incomplete tool_use JSON sets input_parse_error flag and preserves truncated raw_partial_input - TestExtractToolCallsFromProxyBuffer: Tests that extraction logs a warning when input_parse_error is present and truncates the raw input preview appropriately Authored-by: egg
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Re-review: PR #517 - Add test coverage for input_parse_error handling (commit 8cf9961)
This commit adds test coverage for the input_parse_error and raw_partial_input handling that was implemented in previous commits to handle incomplete streaming JSON responses.
Verification of Previous Observation
✅ Non-blocking observation addressed: Test coverage for input_parse_error path
The new tests properly exercise the defensive code path:
TestParseSSEResponse (tests/gateway/test_anthropic_proxy.py:807-861):
test_parse_tool_use_with_incomplete_json: Verifies that incomplete JSON setsinput_parse_error=Trueand preservesraw_partial_inputtest_parse_tool_use_with_long_incomplete_json_truncates: Verifies thatraw_partial_inputis truncated toRAW_INPUT_TRUNCATE_SIZE(1000 chars)
TestExtractToolCallsFromProxyBuffer (tests/shared/egg_contracts/test_transcript_extractor.py:252-329):
test_extract_tool_use_with_input_parse_error: Verifies extraction logs warning wheninput_parse_erroris presenttest_extract_tool_use_with_input_parse_error_long_raw_input: Verifies warning log truncates long raw input preview to 100 chars
All tests pass and properly verify the behavior.
New Tests Overview
The commit adds 61 tests across two files. Beyond the input_parse_error tests, it includes comprehensive coverage for:
- SSE response parsing (
_parse_sse_response) - Streaming request detection (
_is_streaming_request) - Credential injection (
_inject_anthropic_credentials) - Header filtering (
_get_forwarded_headers,_filter_response_headers) - Tool blocking in private mode (
_filter_blocked_tools) - Transcript capture functions (
_capture_non_streaming_response,_capture_streaming_response) - Transcript extraction (metadata, messages, tool calls, token usage)
The tests use appropriate fixtures, mocking, and assertions.
Minor Observation (Non-blocking)
1. Test file location inconsistency
The test file tests/gateway/test_anthropic_proxy.py is placed in tests/gateway/ rather than following the pattern of gateway/tests/test_*.py used by other gateway tests like gateway/tests/test_checkpoint_handler.py and gateway/tests/test_transcript_buffer.py.
This is a minor inconsistency in test organization. Not blocking since both locations work with pytest, but worth noting for future cleanup.
Summary
The commit properly addresses the non-blocking observation from my previous review by adding comprehensive test coverage for the input_parse_error handling path. The tests verify:
- ✅ Incomplete JSON sets
input_parse_errorflag - ✅
raw_partial_inputis preserved (with truncation) - ✅ Warning is logged during extraction
- ✅ Warning log preview is truncated for long inputs
The PR has already been approved by the repository owner. The new changes are sound and add valuable test coverage.
No blocking issues. The PR is ready for merge.
— Authored by egg
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Update project structure documentation to reflect checkpoint-related components added in PR #517 (per-commit checkpoints + API proxy transcript capture): Gateway additions: - checkpoint_handler.py: Per-commit checkpoint capture - transcript_buffer.py: API proxy transcript capture buffer Sandbox additions: - egg-checkpoint CLI symlink in bin/ - checkpoint_cli.py wrapper in egg_lib/ Shared library additions: - checkpoints.py: Checkpoint data models - checkpoint_loader.py: Checkpoint storage and retrieval - checkpoint_cli.py: Checkpoint browsing CLI implementation - transcript_extractor.py: API transcript extraction Triggered by: c7fb529 (#517) Authored-by: egg
|
@james-in-a-box the release build failed for this pr, let's open a new PR fixing the issue: https://github.com/jwbron/egg/actions/runs/21891853356/job/63199377341#step:11:17 |
Build Failure FixI've investigated the release build failure and created a fix. Root Cause: Fix: PR: #521 — Authored by egg |
Update project structure documentation to reflect checkpoint-related components added in PR #517 (per-commit checkpoints + API proxy transcript capture): Gateway additions: - checkpoint_handler.py: Per-commit checkpoint capture - transcript_buffer.py: API proxy transcript capture buffer Sandbox additions: - egg-checkpoint CLI symlink in bin/ - checkpoint_cli.py wrapper in egg_lib/ Shared library additions: - checkpoints.py: Checkpoint data models - checkpoint_loader.py: Checkpoint storage and retrieval - checkpoint_cli.py: Checkpoint browsing CLI implementation - transcript_extractor.py: API transcript extraction Triggered by: c7fb529 (#517) Authored-by: egg Co-authored-by: jwbron <8340608+jwbron@users.noreply.github.com>
Summary
Migrates checkpoint system from per-push to per-commit granularity and replaces Claude Code JSONL transcript extraction with Anthropic API proxy instrumentation.
Key changes:
Architecture
The proxy buffer (
/tmp/egg-transcripts/{container_id}.jsonl) captures:Files Changed
New files:
gateway/transcript_buffer.py- TranscriptBuffer class for API traffic capturegateway/tests/test_transcript_buffer.py- 19 testsgateway/tests/test_checkpoint_handler.py- 15 teststests/shared/egg_contracts/test_transcript_extractor.py- 17 testsModified:
gateway/gateway.py- Instrumentation hooks for API proxygateway/checkpoint_handler.py- Per-commit iteration, proxy buffer integrationgateway/session_manager.py- Buffer cleanup on session endshared/egg_contracts/transcript_extractor.py- Rewritten for proxy buffer onlyshared/egg_contracts/checkpoint_cli.py- Updated help textIssue: closes #509
Test plan
Authored-by: egg