fix: colocate stub script and data file for preprocess_*_batch dispatch - #78
Merged
Merged
Conversation
Root cause (confirmed against josh main and dev, plus the K8s pod entrypoint script): GridSpec._render_preprocess_script() wrote its throwaway stub .josh script into bare system /tmp. Harmless for local preprocess() (no staging step), but preprocessBatch derives its MinIO upload scope from the script's *parent directory* (PreprocessBatchCommand.resolveInputDir) and requires the data file to resolve as a path inside that same directory (resolveDataFileRelativeToInputDir) -- and stages (uploads) the whole directory *before* that check runs. A script in /tmp therefore staged every unrelated file in /tmp (~1500 in one reported case: RStudio session DBs, VSCode caches, other render logs, etc.) before failing with "dataFile is outside input directory". Worse: isolating the script into its own empty temp dir would only make that failure fast instead of slow -- the real data file (e.g. under data/preprocessed/...) still wouldn't be inside it, so the call could never succeed. preprocess-entrypoint.sh confirms the remote pod needs both the script and the data file staged from the same directory (`find $WORK_DIR -name '*.josh'` + `$WORK_DIR/$JOSH_DATA_FILE`). Fixes this by adding GridSpec._render_batch_staging_dir(), which creates a fresh isolated temp directory containing the rendered stub script plus a symlink to the real data file (not a copy -- following batch_orchestrator.assemble_batch_workdir's existing convention, so large geospatial files aren't duplicated on disk). All three preprocess_*_batch() methods now stage through it and clean up the whole directory afterward instead of just unlinking the script. Also fixes JoshCLI.preprocess_batch(): it built its CLI args with Path.resolve(), which follows symlinks -- silently substituting the symlinked data file's real target path (outside the staging directory) for the one josh needs to see. Switched to Path.absolute(), which makes paths absolute without touching symlinks; verified this distinction concretely (a resolve()-vs-absolute() experiment against a real symlink) before landing on it, and added a dedicated regression test that fails under resolve() and passes under absolute(). Testing: 20 new tests, including reimplementations of josh's own PreprocessBatchCommand validation/staging logic (resolveInputDir, resolveDataFileRelativeToInputDir, stageDirectory's Files.walk + isRegularFile) as executable contract checks against the paths this fix actually produces, since the real logic lives in the JAR and can't be exercised without it. Mutation-tested: stashed just this fix and confirmed 11 of 12 new GridSpec-level tests fail against the prior code, reproducing the reported symptom exactly (upload-scope walks found 4122 and 2323 files in this container's /tmp, matching the "~1500 unrelated files" report). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes the bug diagnosed against a downstream user:
preprocessBatchcalls never actually reached a remote target. Root cause confirmed against joshmainanddev, plus the K8s pod entrypoint script (cloud-img/preprocess-entrypoint.sh):GridSpec._render_preprocess_script()wrote its throwaway stub.joshscript into bare system/tmp. Harmless for localpreprocess()(no staging step), butpreprocessBatchderives its MinIO upload scope from the script's parent directory (PreprocessBatchCommand.resolveInputDir()) and requires the data file to resolve as a path inside that same directory (resolveDataFileRelativeToInputDir()) — and stages (uploads) the whole directory before that check runs. A script in/tmpmeant every call staged every unrelated file in/tmp(~1500 in the reported case) before failing withdataFile is outside input directory.data/preprocessed/...) still wouldn't be inside it, so the call could never succeed at all.preprocess-entrypoint.shconfirms the remote pod needs both the script and the data file staged from the same directory (find $WORK_DIR -name '*.josh'+$WORK_DIR/$JOSH_DATA_FILE).Fix
GridSpec._render_batch_staging_dir(): creates a fresh isolated temp directory containing the rendered stub script plus a symlink to the real data file (not a copy — followingbatch_orchestrator.assemble_batch_workdir()'s existing convention, so large geospatial files aren't duplicated on disk). All threepreprocess_*_batch()methods now stage through it and clean up the whole directory afterward instead of just unlinking the script.JoshCLI.preprocess_batch(): it built CLI args withPath.resolve(), which follows symlinks — silently substituting the symlinked data file's real target path (outside the staging directory) for the path josh actually needs to see. Switched toPath.absolute(), which makes paths absolute without touching symlinks. Verified this distinction concretely against a real symlink before landing on it (resolve()returned the real target's dir,absolute()preserved the staging dir).Test plan
tests/test_cli.pyandtests/test_grid.py, including:resolve()vsabsolute()regression test that fails under the old behavior and passes under the fix.script.parent == data_file.parent) for all three formats.cli.preprocess_batch()raises (exception still propagates).resolveInputDir/resolveDataFileRelativeToInputDir/stageDirectory(Files.walk+isRegularFile) logic in Python and run it against the paths this fix actually produces — since the real validation lives in the JAR and can't be exercised without it._core.pyfix and confirmed 11 of the 12 newGridSpec-level tests fail against the prior code — including reproducing the exact reported symptom (upload-scope walks found 4122 and 2323 files in this container's/tmp, matching the "~1500 unrelated files" report).pixi run -e dev pytest tests/ -m "not integration" -q— 1194 passedpixi run -e dev ruff checkon all changed files — 0 new issues (confirmed via before/after diff against the pre-existing baseline)Known related limitation (not fixed here)
amend=Truefor batch dispatch may not fully work: the remote entrypoint always writes fresh to$WORK_DIR/$JOSH_OUTPUT_FILE, and nothing currently stages a pre-existing output.jshdinto that path for the JAR to actually amend against. Flagging as a separate, unverified concern — happy to dig into it further if useful.🤖 Generated with Claude Code