fix(code-bundle): rebuild after source changes - #1508
Conversation
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
@jeffoodchain I used this script to test in devbox and it works, could you please:
|
Remove argument-only async LRU caching from code bundle builders so each invocation re-hashes the current source files. Keep persistent cache reuse for unchanged bundles and add regression test Signed-off-by: Jeff Chung <sh1001309@gmail.com>
09eea72 to
874068d
Compare
|
@popojk thank you. I've added the result to PR description and also signoff. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 874068d9bc
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
|
||
|
|
||
| @alru_cache | ||
| async def build_code_bundle( |
There was a problem hiding this comment.
Retain a content-aware cache for dry-run bundles
When dryrun=True and copy_bundle_to is unset, removing this cache makes every call rescan and recompress the source, allocate a new flyte-tmp-* directory, and leave the copied archive there; the SQLite cache cannot help because its lookup is explicitly skipped for dry runs. This is a production path in connectors/_connector.py, where each remote-storage execution calls build_code_bundle(..., dryrun=True), so a long-lived connector worker now accumulates one full code bundle per execution and can eventually exhaust local disk while repeatedly blocking on compression. Preserve reuse after the content digest is recomputed, or clean up the generated bundle after the connector uploads it.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
I think this might be an issue, @popojk do you think we should solve this in this PR as well?
There was a problem hiding this comment.
@jeffoodchain Please also solve this in this PR
Without the async LRU cache the connector rebuilds a tarball on every remote execution, and each one lands in a random local path that nobody deletes. Solved this by using a temporary directory that is removed once the upload finishes. Signed-off-by: Jeff Chung <sh1001309@gmail.com>
Main removed the alru_cache on build_code_bundle entirely (rebuild after source changes), which solves the stale-bundle problem at the root. Drop this branch's refresh_code_bundle_cache() and skip_cache memo-bypass — they managed a cache that no longer exists. control_plane_available() and Run.first_failure() / ActionDetails.error_message remain. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NwQixBcyR5va6BC75jaQx3
…ent edit-and-relaunch loops (#1522) ## Why The agent-mediated forking example (unionai/unionai-examples#308) — an agent task that launches a workflow, observes the failure, patches the workflow's source on disk, reloads it, and `fork()`s the failed run with the fixed code — had to reach into SDK internals: 1. `isinstance(get_controller(), RemoteController)` to decide whether launching/forking real runs is possible, or whether to fall back to running the workflow inline. 2. Hand-rolled `Action.listall(...FAILED...)` iteration to find which step failed and its error message. 3. `flyte._code_bundle.bundle.build_code_bundle.cache_clear()` to make the next fork ship the edited working tree — **no longer needed**: #1508 removed the code-bundle memoization entirely, so every launch re-bundles from disk. This PR originally shipped a `flyte.refresh_code_bundle_cache()` for that; it was dropped when merging main, since the cache it managed no longer exists. ## What **`flyte.is_control_plane_available()`** — True when the process can submit work to a control plane (launch real runs whose actions can be awaited and replayed/forked). Inside a task, the orchestration mode decides (`remote`/`hybrid` → True, `local` → False, even when a client is configured — `flyte run --local` configures one too); outside a task, a configured client decides. This replaces the isinstance-on-internal-controller probe with the `TaskContext.mode` the runtime already maintains. **`Run.first_failure()` and `ActionDetails.error_message`** — the observation half of a repair loop: which step of a run failed, and why. `first_failure()` returns the `ActionDetails` of the first failed action in creation order, preferring a failed sub-action over the failed root (whose error usually just repeats the sub-action's); `error_message` is the failed action's message or `""`. With these, the example's loop reduces to: ```python run = await flyte.run.aio(wf.main, n_records=n_records) await run.wait.aio(quiet=True) if failure := await run.first_failure.aio(): patch_workflow_source(failure.task_name, failure.error_message) importlib.reload(sys.modules["workflow"]) run = await fork.aio(run.name, task_template=wf.main) ``` ## Testing - New unit tests: `is_control_plane_available()` across uninitialized/client/local/remote/hybrid contexts; `Run.first_failure()` sub-action preference, root fallback, and no-failure; `error_message`. - Existing `code_bundle`, `remote`, `deploy`, and `cli/test_run.py` suites pass (the only failures are pre-existing on `main`: the two `loaded_modules` discovery tests). - `ruff`, `mypy`, and `ty` clean via pre-commit hooks. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01NwQixBcyR5va6BC75jaQx3 --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Fixes flyteorg/flyte#7923
Bug:
build_code_bundlememoizes only its arguments → long-lived processes reuse stale source after files are editedSummary
build_code_bundleandbuild_code_bundle_from_relative_pathsare wrapped in@alru_cache. The cache key contains function arguments such asfrom_dir,copy_style, andadditional_files, but it does not contain the selected files or their contents.In a long-lived process, the first
run()builds and caches aCodeBundle. If source files are then edited and the task is run or forked again with the same bundling arguments,alru_cachereturns the original bundle before the working tree is scanned again.The later run therefore executes the pre-edit source without rebuilding the bundle or emitting a warning.
Impact
run()→ edit source →fork()can execute the original source instead of the edited source.skip_cache=Truedoes not provide a complete guarantee because repeated calls using the sameskip_cache=Truearguments can themselves be memoized.Root cause
src/flyte/_code_bundle/bundle.pymemoizes the entire bundling operation:The same applies to the explicit-path builder:
The content-sensitive portion of the operation runs inside the memoized function:
On an
alru_cachehit, execution returns beforelist_files_to_bundleorlist_relative_files_to_bundlecan recompute the digest.The bundler already has a content-addressed persistent cache after file discovery:
This change removes the outer argument-only memoization while preserving the digest-backed persistent cache. Each invocation now scans and hashes the current source state; unchanged source still reuses the existing uploaded bundle and skips compression and upload.
Regression tests modify a source file between calls made with identical arguments and verify that:
computed_versionchanges;cache_clear().Test script result
I ran this locally on my Mac (M1 pro), and here are the logs.
cc @popojk