Skip to content

Python: FileCheckpointStorage save/load symmetry (#8181) - #8214

Merged
Evan Mattson (moonbox3) merged 7 commits into
microsoft:mainfrom
FOWEPJF255:fix/file-checkpoint-save-load-symmetry-8181
Sep 15, 2026
Merged

Evan Mattson (moonbox3) merged 7 commits into
microsoft:mainfrom
FOWEPJF255:fix/file-checkpoint-save-load-symmetry-8181

Conversation

@FOWEPJF255

Copy link
Copy Markdown
Contributor

Motivation & Context

FileCheckpointStorage could save checkpoints that the same storage could not restore, surface raw JSONDecodeError/UnicodeDecodeError on load, and disagree between list_checkpoints and list_checkpoint_ids when decoding failed. This breaks durable pause/resume and makes failures harder to handle.

Fixes #8181.

Description & Review Guide

  • What are the major changes?
    • Validate encoded checkpoints against this storage's allowed_checkpoint_types before writing (including encoding failures), so save refuses payloads that load would reject.
    • Wrap invalid JSON and invalid UTF-8 on load as WorkflowCheckpointException.
    • Make list_checkpoint_ids delegate to list_checkpoints so both share one decode path.
  • What is the impact of these changes?
    • Save/load/list stay consistent; callers get a single exception type for corrupt files and unrestorable payloads.
  • What do you want reviewers to focus on?
    • Save-time encode+restore validation, UTF-8/JSONDecodeError wrapping on load, and list_checkpoint_ids delegation.

Related Issue

Fixes #8181

This replaces closed PR #8193 (closed for missing PR template). Same issue; no other open PR for #8181.

Contribution Checklist

  • The code builds clean without any errors or warnings
  • All unit tests pass, and I have added new tests where possible
  • The PR follows the Contribution Guidelines
  • This PR is linked to an issue and there is no other open PR for this issue (see Related Issue above).
  • This is not a breaking change. If it is a breaking change, add the breaking change label (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.

Copilot AI 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.

🟡 Changes recommended

save() still writes checkpoint JSON without an explicit UTF-8 encoding while load() now forces UTF-8, which can reintroduce save/load asymmetry on non-UTF-8 default locales.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR improves durability and debuggability of the Python FileCheckpointStorage by enforcing save/load symmetry, standardizing exception behavior for corrupt checkpoint files, and aligning listing APIs so they don’t disagree when decoding fails.

Changes:

  • Validate at save-time that an encoded checkpoint can be decoded under the same storage’s allowed_checkpoint_types, preventing “save succeeds, load fails” scenarios.
  • Wrap invalid JSON / invalid UTF-8 during load() into WorkflowCheckpointException (per the CheckpointStorage contract).
  • Make list_checkpoint_ids() delegate to list_checkpoints() so both use the same decode/filter path.
File summaries
File Description
python/packages/core/agent_framework/_workflows/_checkpoint.py Adds save-time restore validation, wraps JSON/UTF-8 read failures on load, and aligns ID listing with decoded listing.
python/packages/core/tests/workflow/test_checkpoint.py Adds regression tests for invalid JSON/UTF-8 on load and for list ID alignment with decode filtering.
python/packages/core/tests/workflow/test_checkpoint_unrestricted_pickle.py Adds test ensuring file storage refuses to save checkpoints it cannot restore under its own allowed-types policy.
Review details

Suppressed comments (2)

python/packages/core/agent_framework/_workflows/_checkpoint.py:472

  • Since list_checkpoint_ids() now delegates to list_checkpoints(), listing IDs inherits list_checkpoints() file-reading behavior. list_checkpoints() currently opens checkpoint files without encoding="utf-8", while load() explicitly uses UTF-8; this can make listing locale-dependent and cause list_checkpoint_ids()/get_latest() to miss checkpoints that load() could otherwise read (or vice versa) when non-ASCII is present.
        checkpoints = await self.list_checkpoints(workflow_name=workflow_name)
        return [checkpoint.checkpoint_id for checkpoint in checkpoints]

python/packages/core/agent_framework/_workflows/_checkpoint.py:382

  • This error message is also used for UnicodeDecodeError (invalid UTF-8), but it only mentions JSON. That can be misleading when the file is valid JSON in another encoding or simply not UTF-8.
            raise WorkflowCheckpointException(
                f"Checkpoint file for {checkpoint_id} is not valid JSON and cannot be loaded."
            ) from ex
  • Files reviewed: 3/3 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

Comment thread python/packages/core/agent_framework/_workflows/_checkpoint.py
@FOWEPJF255

Copy link
Copy Markdown
Contributor Author

Follow-up: save() and list now open checkpoint JSON with explicit encoding=utf-8 to match load().

lhl-lhi (lhl-lhi) and others added 3 commits September 10, 2026 21:44
Include encoding in save-time validation, wrap invalid UTF-8 on load,
and delegate list_checkpoint_ids to list_checkpoints.
Match load()'s explicit UTF-8 encoding so save/load stay symmetric on non-UTF-8 default locales.
@FOWEPJF255
lsmlhi_25 (FOWEPJF255) force-pushed the fix/file-checkpoint-save-load-symmetry-8181 branch from f2ba45a to b443bbf Compare September 10, 2026 13:44
…icrosoft#8181)

Copilot review: UnicodeDecodeError should not claim invalid JSON.
@FOWEPJF255

Copy link
Copy Markdown
Contributor Author

Follow-up: save()/list/load all use explicit UTF-8; load distinguishes UnicodeDecodeError vs JSONDecodeError. Please re-review when convenient.

@FOWEPJF255

Copy link
Copy Markdown
Contributor Author

Thanks for the approval. Branch is up to date / CI running; ready to merge when checks are green.

…8181)

Package Checks ruff rule blocking-path-method-in-async-function failed
on Path(tmpdir).glob in an async test. Use asyncio.to_thread like other
core tests.
@ktz03

Copy link
Copy Markdown
Contributor

lsmlhi_25 (@FOWEPJF255) Already has Eduard van Valkenburg (@eavanvalkenburg) approval and the UTF-8 save/load follow-up. Flagging here in case this just needs a final maintainer merge once required workflows are green.

@FOWEPJF255

Copy link
Copy Markdown
Contributor Author

Friendly ping: this PR is mergeable and waiting on review (FileCheckpointStorage save/load symmetry for #8181). Happy to address any feedback.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good—thanks!

@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Sep 14, 2026
@ktz03

Copy link
Copy Markdown
Contributor

Eduard van Valkenburg (@eavanvalkenburg) Thanks for the re-approval — CI looks green / mergeable on this head whenever you're ready to merge.

@moonbox3
Evan Mattson (moonbox3) added this pull request to the merge queue Sep 15, 2026
Merged via the queue into microsoft:main with commit 91b3c8b Sep 15, 2026
40 checks passed
Manjunath Janardhan (manjunathshiva) added a commit to manjunathshiva/agent-framework that referenced this pull request Sep 15, 2026
microsoft#8214 landed and rewrote `save()`, which this branch also rewrites. Resolved in
favour of this branch's machinery for the conflicting hunk -- their
`_write_atomic` is the pre-rewrite version -- while deliberately carrying their
two changes forward rather than letting "keep ours" discard them:

* `encoding="utf-8"` on the write. Their fix landed inside the conflicting hunk,
  so resolving in our favour dropped it; it is restored on this branch's
  `os.fdopen`. This is a real bug, not a style change: `json.dump(...,
  ensure_ascii=False)` writes non-ASCII raw, and the platform default is cp1252
  on Windows, so a checkpoint containing CJK text or emoji raised
  `UnicodeEncodeError`. Both read sites came through the automatic merge already
  UTF-8; fixing only one side would have traded a loud write-time error for
  silent read-time corruption. Verified by round-tripping CJK, accented and
  emoji codepoints.

* Their save-time encode/decode validation, which the automatic merge kept. It
  raises before `_enqueue_write`, so a rejected checkpoint never takes a
  destination ticket, and it sits outside the critical section so it does not
  extend how long a destination is held.

Checked rather than assumed: no test was lost on either side (71 here + main's,
74 after), and all 25 tests this branch adds still fail against the new `main`,
so the description's teeth claim still holds. The two newly uncovered lines in
the file are microsoft#8214's own exception branch, not this branch's.
Manjunath Janardhan (manjunathshiva) added a commit to manjunathshiva/agent-framework that referenced this pull request Sep 15, 2026
Two conflicts, both in imports, both resolved by keeping what this branch needs:

* `_checkpoint.py` -- `main` dropped `import time`; this branch still needs it for
  `_replace_with_retry`'s backoff, which `main` does not have. Verified `time.sleep`
  is still referenced rather than assuming.

* `test_checkpoint.py` -- `main` added a module-level `import threading` while this
  branch added `import time` and the `ConcurrentFuture` alias. All three kept, and
  the function-local `import threading` added earlier today is now redundant so it
  is removed in favour of the module-level one.

Checked: no test lost on either side (74 here + main's, 75 after), and both this
branch's machinery and microsoft#8214's `encoding="utf-8"` and save-time validation are
still present.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

python Usage: [Issues, PRs], Target: Python

Projects

None yet

6 participants