workflow-fix: flock + additive-merge pod.py config --sync (SSH-config drop race)
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 workflow-fix candidate raised on task #813 (emitting agent: orchestrator).
Goal
Serialize pod.py config --sync's rewrite of ~/.ssh/config + the MCP env block against concurrent writers, and make it additive from the canonical repo-root pods.conf so a sync can never drop another session's live pod entry.
Workflow gap
- Bug observed: Twice within ~1.5h (2026-07-02, task #813), a concurrent session's
pod.py config --syncdropped theHost pod-813entry from~/.ssh/config(and earlier the SSH MCP env reverted to a months-stale server list) whilescripts/pods.confstill contained pod-813 — producing falsedeadpoll verdicts on a healthy run and requiring manual re-sync mid-monitoring. - Why it is a workflow gap: the sync's read-modify-write of the managed SSH-config block is not flock-serialized (unlike task.py's mutations), and a session syncing from a stale view (e.g. a worktree copy of pods.conf, or an in-memory list read before another session's provision) rewrites the managed block without entries added concurrently.
- Confidence (emitter): medium (mechanism inferred from two occurrences + the stale MCP-server-list symptom; the exact racing writer not identified).
Proposed change (candidate diff sketch — refine in planning)
- In pod_config.py sync path:
-
- hold flock on ~/.task-workflow/pod-config.lock around the whole read-merge-write;
-
- resolve pods.conf ONLY at the repo-root canonical path (never cwd/worktree-relative);
-
- re-READ pods.conf after acquiring the lock (never sync from a pre-lock snapshot);
-
- make the managed-block rewrite a MERGE: preserve any Host entry present in the
-
freshly-read pods.conf; only remove entries absent from it.
Scope / surfaces
- Primary target:
scripts/pod_config.py,scripts/pod.py(config --sync path) - Grep the workflow surface for the managed-block rewrite before editing and update every hit; list them in the plan.
Constraints / invariants
- Workflow-surface only — never experiment code,
configs/, ortasks/. scripts/workflow_lint.py --check-askspasses; ruff on touched files passes.- This session runs under
EPM_WORKFLOW_FIX_SESSION=1and carries aworkflow_fix_target:Provenance line — it MUST NOT auto-route any of its own subagents' workflow-fix candidates (recursion guard).
Provenance
- workflow_fix_target: scripts/pod_config.py, scripts/pod.py
- fingerprint: (computed by wrapper)
target_file: scripts/pod_config.py, scripts/pod.py
bug_observed: Concurrent pod.py config --sync rewrites dropped the live pod-813 Host entry from ~/.ssh/config twice within ~1.5h while pods.conf still contained it, producing false dead poll verdicts mid-run (task #813, 2026-07-02).
why_workflow_gap: The sync's read-modify-write of the managed SSH-config + MCP env block is not flock-serialized and can rewrite from a stale pods.conf view, silently removing entries added by concurrent sessions.
proposed_change: Flock-guard the whole sync, re-read the canonical repo-root pods.conf under the lock, and make the managed-block rewrite additive-merge (only remove entries absent from the freshly-read pods.conf).
diff_sketch: |
- lock = open(os.path.expanduser("~/.task-workflow/pod-config.lock"), "w")
- fcntl.flock(lock, fcntl.LOCK_EX)
- conf = read_pods_conf(REPO_ROOT / "scripts/pods.conf") # re-read under lock
- merged = merge_managed_block(existing_ssh_config, conf) # additive confidence: medium related_task: #813