Skip to content

fix: make project update flow atomic#8805

Merged
Soulter merged 5 commits into
AstrBotDevs:masterfrom
Soulter:codex/atomic-update-flow
Jun 15, 2026
Merged

fix: make project update flow atomic#8805
Soulter merged 5 commits into
AstrBotDevs:masterfrom
Soulter:codex/atomic-update-flow

Conversation

@Soulter

@Soulter Soulter commented Jun 15, 2026

Copy link
Copy Markdown
Member

Summary

  • split dashboard and core update downloads from extraction/application
  • download and verify both update archives before applying either package
  • add tests for successful ordering, failed core download, and failed package verification

Tests

  • ruff check astrbot/core/utils/io.py astrbot/core/updator.py astrbot/dashboard/services/update_service.py astrbot/dashboard/api/app.py tests/test_dashboard.py tests/test_fastapi_v1_dashboard.py
  • uv run pytest tests/test_dashboard.py -k "test_do_update"

Note: full ruff check . is currently blocked by an existing untracked packages/astrbot-sdk ASYNC220 warning outside this PR.

Summary by Sourcery

Make the project update process atomic by separating download, verification, and application of core and dashboard updates, and by adding coverage for failure scenarios.

New Features:

  • Allow downloading AstrBot core update packages without immediately applying them.
  • Support downloading dashboard assets without extraction and extracting them separately.

Bug Fixes:

  • Prevent partial updates by ensuring core and dashboard archives are both downloaded and verified before any files are applied.
  • Avoid leaving temporary update artifacts by cleaning up downloaded archives after the update completes or fails.

Enhancements:

  • Refactor the update service to orchestrate separate download, verify, and apply stages with progress tracking.
  • Inject dashboard extraction and new core updater operations into the dashboard service to improve testability and control flow.

Tests:

  • Extend dashboard update tests to cover successful ordering, failed core download, and failed package verification paths.

@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. area:core The bug / feature is about astrbot's core, backend feature:updater The bug / feature is about astrbot updater system labels Jun 15, 2026

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue, and left some high level feedback:

  • The extract_dashboard_func parameter and call_extract_dashboard helper are annotated as Callable[..., Awaitable[Any]], but the default extract_dashboard implementation is synchronous; consider broadening these type hints (e.g., Callable[..., Any]) to better reflect actual usage and avoid misleading annotations.
  • In download_update_package, the path parameter defaults to "temp.zip" without ensuring the parent directory exists; if this is ever called outside update_project, consider mirroring the ensure_dir(zip_path.parent) logic used for dashboard downloads to make the method more robust to different working directories.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `extract_dashboard_func` parameter and `call_extract_dashboard` helper are annotated as `Callable[..., Awaitable[Any]]`, but the default `extract_dashboard` implementation is synchronous; consider broadening these type hints (e.g., `Callable[..., Any]`) to better reflect actual usage and avoid misleading annotations.
- In `download_update_package`, the `path` parameter defaults to `"temp.zip"` without ensuring the parent directory exists; if this is ever called outside `update_project`, consider mirroring the `ensure_dir(zip_path.parent)` logic used for dashboard downloads to make the method more robust to different working directories.

## Individual Comments

### Comment 1
<location path="astrbot/core/utils/io.py" line_range="506-515" />
<code_context>
+        extract_dashboard(zip_path, extract_path)
+
+
+def extract_dashboard(zip_path: str | Path, extract_path: str | Path = "data") -> None:
+    """Extract a downloaded dashboard archive.
+
+    Args:
+        zip_path: Dashboard zip archive path.
+        extract_path: Directory where the archive contents should be extracted.
+
+    Returns:
+        None.
+    """
+
+    ensure_dir(extract_path)
     with zipfile.ZipFile(zip_path, "r") as z:
         z.extractall(extract_path)
</code_context>
<issue_to_address>
**🚨 issue (security):** Consider hardening dashboard extraction against path traversal in ZIP entries.

Because this helper is now the single extraction point, it’s worth validating entries before `extractall`. `zipfile` will write outside `extract_path` if filenames contain `../` or absolute paths. A common mitigation is to iterate over `z.infolist()`, compute `resolved = (Path(extract_path) / member.filename).resolve()`, verify `resolved.is_relative_to(Path(extract_path).resolve())` (or equivalent), raise on violation, and then call `z.extract(member, extract_path)` only for validated members. This preserves behavior for valid archives while preventing path traversal from malicious or corrupted ZIPs.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread astrbot/core/utils/io.py

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the update process for AstrBot Core and its dashboard by separating the download, verification, and application phases, ensuring that update packages are fully downloaded and verified before being applied. The feedback highlights critical performance issues where synchronous, I/O-heavy operations—specifically zip extraction, package verification (testzip), and applying the update package—are executed directly on the main asyncio event loop. To prevent the dashboard from becoming unresponsive, these blocking operations should be offloaded to separate threads using asyncio.to_thread.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +51 to +55
async def call_extract_dashboard(*args, **kwargs):
result = extract_dashboard(*args, **kwargs)
if inspect.isawaitable(result):
return await result
return result

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The extract_dashboard function is synchronous and performs heavy I/O and CPU-bound zip extraction. Calling it directly inside an async def function blocks the single-threaded asyncio event loop, which can make the entire dashboard unresponsive during the extraction process.

We should run it in a separate thread using asyncio.to_thread to keep the event loop responsive, while still safely handling potential coroutine/awaitable mocks in tests.

Suggested change
async def call_extract_dashboard(*args, **kwargs):
result = extract_dashboard(*args, **kwargs)
if inspect.isawaitable(result):
return await result
return result
async def call_extract_dashboard(*args, **kwargs):
import asyncio
if inspect.iscoroutinefunction(extract_dashboard):
return await extract_dashboard(*args, **kwargs)
result = await asyncio.to_thread(extract_dashboard, *args, **kwargs)
if inspect.isawaitable(result):
return await result
return result

Comment thread astrbot/dashboard/services/update_service.py Outdated
Comment thread astrbot/dashboard/services/update_service.py Outdated
@Soulter Soulter merged commit dd46cce into AstrBotDevs:master Jun 15, 2026
21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:core The bug / feature is about astrbot's core, backend feature:updater The bug / feature is about astrbot updater system size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant