workflow-fix: gotchas entry — out-arg FILE-vs-DIR kind; misnested deliverables pass .exists() checks
kind: infra#wf-fix#wf-fix-fp:1e51018bc728
track:Highlight text on any card (body, plan, or an event) to anchor a comment, or leave a whole-task comment here. Mention @claude to summon a reply.
No comments yet.
Overview / Motivation
Auto-filed by the workflow-fix-on-bug protocol from a failure-lesson gotcha_candidate: yes block raised on task #1776 (emitting agent: experiment-implementer, crash-fix cycle 5).
Goal
Add a .claude/rules/gotchas.md entry documenting the out-arg FILE-vs-DIRECTORY kind trap: a deliverable file path passed as a script's out-DIRECTORY arg misnests outputs one level deep while every .exists() / [[ -e ]] completeness check passes on the misnested shape — the crash surfaces a full phase later at upload.
Workflow gap
- Bug observed: #1776's
dispatch.shpassed the plan deliverable's FILE path as phase3's--eval-outDIRECTORY arg.mkdir(parents=True)created a directory NAMED like the file; the real outputs landed at.../steered_shift_summaries.json/steered_shift_summaries.json; the phase's own completeness assert ((eval_out / "x.json").exists()) passed BY COINCIDENCE of the nesting; the upload list's[[ -e ]]guard passed the directory; onlyCommitOperationAdd's is_file check crashed — one full phase later, on 8×H100, costing a launch cycle. A sibling upload-list entry pointing at a never-written path was silently-e-skipped in the same block (the #825 class). - Why it is a workflow gap: gotchas.md covers the upload-side file/folder API branch (
hub._uploadValueError, ~L300, #595) but nothing documents the dispatcher-side out-ARG kind contract or the.exists()-satisfied-by-a-directory assert trap — dispatcher-invokes-phase-script is the standard experiment shape, so the class recurs. - Confidence (emitter): high
- verified-at-filing:
grep -n -i "is_file\|file-vs-dir\|file vs dir\|mkdir.*named" .claude/rules/gotchas.md→ 1 hit in 1 file (L300, the hub._upload per-file API branch — upload-side, distinct trap; NO entry covers the dispatcher out-arg kind / misnesting-passes-exists class; absence claim) (2026-07-29)
Proposed change (candidate diff sketch — refine in planning)
+ - **An out-path arg's FILE-vs-DIRECTORY kind is part of the call contract —
+ and a misnested deliverable PASSES every .exists()/[[ -e ]] check.** A
+ dispatcher that passes a deliverable FILE path as a script's out-DIR arg
+ makes mkdir(parents=True) create a dir NAMED like the file; outputs land
+ one level deep (.../x.json/x.json); completeness asserts of the form
+ (out_dir/"x.json").exists() pass BY COINCIDENCE; [[ -e ]] upload-list
+ guards pass directories; the crash surfaces a phase later at
+ CommitOperationAdd (#1776 p3_upload, one 8xH100 launch cycle). Rules:
+ (i) check what the script DOES with each out arg (mkdir => dir;
+ open/write => file) when composing dispatcher invocations, and
+ class-sweep all phases' out args once; (ii) deliverable checks use
+ is_file()/[[ -f ]], never exists()/[[ -e ]]; (iii) scripts refuse
+ file-shaped out-dir args at argparse time; (iv) misnest repair branches
+ are exact-shape-guarded and RELOCATE, never delete. Sibling (upload-side
+ API kind): the hub._upload per-file entry below/above (#595). Long-form
+ twin: .claude/agent-memory/experiment-implementer/feedback_out_arg_file_vs_dir_kind.md
Scope / surfaces
- Primary target:
.claude/rules/gotchas.md - Place near the hub._upload file/folder entry (~L300) so the kind-contract family clusters.
Constraints / invariants
- Workflow-surface only — never experiment code,
configs/, ortasks/. scripts/workflow_lint.pyno-flags run passes; gotchas.mdpaths:frontmatter untouched unless the trigger set genuinely widens.- This session runs under
EPM_WORKFLOW_FIX_SESSION=1and carries aworkflow_fix_target:Provenance line — it MUST NOT auto-route its own subagents' workflow-fix candidates (recursion guard).
Provenance
- workflow_fix_target: .claude/rules/gotchas.md
- fingerprint: 1e51018bc728
Activity