YAshhh29 opened a new pull request, #75916:
URL: https://github.com/apache/airflow/pull/75916

   On Airflow before 3.3, durable agent tasks keep their journal in a JSON file 
through ObjectStorage. When that storage failed, or the file was damaged, the 
journal could fail a task whose work had already succeeded, or quietly throw 
away the previous attempt's steps. This makes that backend behave like the task 
state store one already does: storage problems are logged and skipped, and they 
never fail the task.
   
   **What was wrong**
   
   `DurableStorageProtocol` says a backend may skip a write it can't make 
without failing the step, and `TaskStateStoreDurableStorage` (Airflow >= 3.3) 
already does. `DurableStorage` didn't: an error from `_write` went straight out 
of `save_step`. Most saves happen after the work they record, so a failed write 
could:
   
   - fail a task whose work had already succeeded, when the journal lists more 
than one agent run, because `DurableJournal.cleanup` rewrites the run list 
through `save_step`
   - raise a storage error in place of the agent's own error, because 
`DurableRun.fail` saves the run meta before re-raising
   - fail a tool step after the tool's side effect had already run
   
   Reads had problems too. A read that failed with an `OSError` was treated as 
an empty journal without a warning, so the next save overwrote the previous 
attempt's steps. A file holding JSON that isn't an object failed the task with 
an `AttributeError` on the first lookup.
   
   **What changes**
   
   - A failed write is skipped with a warning and `save_step` returns `False`, 
so a retry runs that step again. In memory the key keeps its previous value, so 
a later save doesn't write the skipped entry either.
   - If the journal file can't be read, for a reason other than being missing 
or invalid, it's left in place for the next attempt. This attempt runs live 
without recording, and `delete_steps` leaves the file alone.
   - A file that isn't a valid journal is logged and replaced: one cut off 
mid-write, JSON that isn't an object, or bytes that aren't UTF-8 text. The last 
case is the second commit. Without it, such a file counted as unreadable, so 
that task instance would never journal again.
   
   The durable execution docs now describe this for the ObjectStorage backend 
too.
   
   **Testing**
   
   On Linux (WSL, Ubuntu 24.04) the durable tests give 302 passed and 3 
skipped, because the optional `agents` and `strands` packages aren't installed. 
There's also 1 error, in 
`TestSummary.test_summary_names_the_tools_a_retry_runs_again`. That test runs 
on the in-memory test storage and doesn't touch this code; the error is a local 
fixture import (`cap_structlog` needs `airflow_shared`, which isn't installed 
in a plain uv env).
   
   I wrote the non-UTF-8 case test-first: it failed before the fix and passes 
after. While developing the first commit, each new guard in `storage.py` was 
removed one at a time, and a test failed every time. The prek pre-commit hooks 
pass.
   
   <details>
   <summary>Test runs (WSL, Ubuntu 24.04, Python 3.12.16)</summary>
   
   New case before the fix (`f8165fb566` with only the test change applied):
   
   ```
   $ uv run --project providers/common/ai pytest 
providers/common/ai/tests/unit/common/ai/durable/test_storage.py -k corrupt -v
   ...test_a_corrupt_journal_is_reported_and_started_over[cut off mid-write] 
PASSED
   ...test_a_corrupt_journal_is_reported_and_started_over[not a JSON object] 
PASSED
   ...test_a_corrupt_journal_is_reported_and_started_over[not UTF-8 text] FAILED
   E   AssertionError: assert False
   E    +  where False = save_step('k', {...})
   1 failed, 2 passed, 20 deselected
   ```
   
   After the fix:
   
   ```
   ...test_a_corrupt_journal_is_reported_and_started_over[not UTF-8 text] PASSED
   3 passed, 20 deselected
   ```
   
   Full durable suite:
   
   ```
   $ uv run --project providers/common/ai pytest 
providers/common/ai/tests/unit/common/ai/durable -q
   302 passed, 3 skipped, 1 warning, 1 error
   ERROR 
...test_journal.py::TestSummary::test_summary_names_the_tools_a_retry_runs_again
 - ModuleNotFoundError: No module named 'airflow_shared'
   ```
   
   </details>
   ---
   
   ##### Was generative AI tooling used to co-author this PR?
   
   - [X] Yes — Claude Code (Opus 5.5)
   
   I used Claude Code to help trace the journal code paths and to draft this 
description. I ran the tests on Linux myself, wrote the non-UTF-8 test and fix 
test-first, and went through every change before pushing.
   
   Generated-by: Claude Code (Opus 5.5) following [the 
guidelines](https://github.com/apache/airflow/blob/main/contributing-docs/05_pull_requests.rst#gen-ai-assisted-contributions)


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to