personal memory agent
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Fix follow-up image lifecycle in describe.py vision analysis

The follow-up image was being closed immediately after creating async
requests, causing "Operation on closed image" errors when the batch
later tried to process them. This broke category-specific content
extraction (browsing, meeting, etc.) leaving empty content fields.

- Store follow-up images in frame_images dict keyed by frame_id
- Close image only when all follow-ups for that frame complete
- Removes stale references issue from multiple request objects

Bug was introduced in 03666da (Dec 24, 2025) when conditional close
logic was changed to unconditional.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

+10 -1
+10 -1
observe/describe.py
··· 427 427 428 428 # Track frames by frame_id for merging follow-up results 429 429 frame_results = {} # frame_id -> result dict 430 + frame_images = {} # frame_id -> PIL Image (for follow-up cleanup) 430 431 431 432 # Stream results as they complete, with retry logic 432 433 async for req in batch.drain_batch(): ··· 526 527 full_img = Image.open(io.BytesIO(req.frame_bytes)) 527 528 req.pending_follow_ups = len(follow_ups) 528 529 530 + # Store image in frame_images for cleanup (single reference) 531 + frame_images[req.frame_id] = full_img 532 + 529 533 # Close initial image since DESCRIBE is complete 530 534 if hasattr(req, "initial_image") and req.initial_image: 531 535 req.initial_image.close() ··· 570 574 f"{', '.join(cat for cat, _ in follow_ups)}" 571 575 ) 572 576 573 - full_img.close() 577 + # Don't close full_img here - requests are async and need it open 574 578 continue # Don't output yet, wait for follow-ups 575 579 576 580 # Handle follow-up completion for parallel requests ··· 615 619 616 620 # Clean up frame_results entry 617 621 del frame_results[req.frame_id] 622 + 623 + # Close follow-up image now that all requests are done 624 + if req.frame_id in frame_images: 625 + frame_images[req.frame_id].close() 626 + del frame_images[req.frame_id] 618 627 619 628 # Aggressively clear heavy fields 620 629 req.frame_bytes = None