Skip to content

fix: dev-workflow helpers assume a laptop checkout — one fails open #225

Closed

Description

@Jammy2211

Overview

Three defects, one root cause: the dev-workflow helper scripts resolve paths under $HOME/Code/PyAutoLabs, which does not exist in a cloud, web or CI session. All three were hit in sequence during the PyAutoBrain#224 session on 2026-08-10, and each silently did the wrong thing rather than complaining.

Filed high because #2 is a safety defect, not an inconvenience: a guard that returns "all clear" when it cannot read its input is worse than no guard, because the workflow documents it and skills act on its answer.

Plan

  • prompt_sync_push — push the current branch, or refuse when the checkout is not on main. It currently hardcodes git push origin main.
  • worktree_check_conflict — fail closed when the registry cannot be resolved, naming the path it tried. It currently returns 0, meaning "no conflict".
  • test_skill_install.py — skip with a reason on the web-github installer path instead of failing. It currently fails in any cloud session and passes in real CI.

1. prompt_sync_push hardcodes git push origin main

PyAutoMind scripts/prompt_sync.sh — the helper every Mind-writing skill is told to call. create_issue step 6 and start_dev step 7 both instruct the agent to call it. A branch-scoped session that follows the documented steps verbatim pushes Mind straight to main, bypassing review. In the #224 session this was caught only because the helper was inspected before use; the documented path would have violated the session's branch requirement.

2. worktree_check_conflict fails open — the important one

bin/worktree.sh, in worktree_list_claimed:

if [[ ! -f "$active" ]]; then
  return 0
fi

No registry → returns 0 → worktree_check_conflict reports no conflict. The guard cannot distinguish "nothing claims this repo" from "I could not read the registry", and answers the former.

Reproduced: worktree_check_conflict reconcile-upstream-repo-mode PyAutoBrain PyAutoMind returned 0 and was recorded as a clean check. Re-run with PYAUTO_MAIN=/workspace it worked and returned the same verdict — but the first answer was worth nothing, and had a conflict existed the session would have started conflicting work on a green light. This is the guard that serialises two agents wanting the same repo.

3. test_skill_install.py fails locally, passes in CI

Two tests assert on installer output that only appears on the local-dev path (assert "SKIP intake (Codex skill" in result.stdout). bin/install.sh already knows the difference — it prints Environment: local-dev vs Environment: web-github / ci-only — and skips the skill-symlink work on the latter.

Consequence, observed: Heart is unreachable from a cloud session, so ship_library falls back to pytest -x and treats any failure as RED. These two failures made that gate spuriously RED on #224 and cost a human acknowledgement to override. CI on that PR then went green on both legs, confirming the diff was never implicated.

The unifying point

bin/install.sh already detects and prints its execution environment, and WORKFLOW.md already names them (local-dev, web-github, ci-only). The helpers should consult that same notion and either adapt or fail closed. None of the three should silently do the wrong thing.

Test Plan

  • prompt_sync_push on a non-main branch pushes that branch, or refuses; never pushes to main from a branch-scoped checkout. Covered by a test.
  • worktree_check_conflict returns non-zero when it cannot resolve active.md, naming the path it tried. Covered by a test pointing $PYAUTO_MAIN at an empty dir. The "registry present, no claim" case still returns 0.
  • test_skill_install.py passes on both paths — skipping with a reason on web-github, still asserting fully on local-dev.
  • pytest tests/ green in a cloud session, so the ship_library fallback gate stops producing false REDs.

Affected Repositories

  • PyAutoBrain (primary) — bin/worktree.sh, tests/test_skill_install.py
  • PyAutoMind — scripts/prompt_sync.sh

Suggested branch: feature/dev-workflow-helpers-laptop-paths

Branched from main, independent of #224 (disjoint files), so the two can land in either order.

Original Prompt

Filed at PyAutoMind draft/bug/pyautobrain/dev_workflow_helpers_assume_laptop_paths.md.

Activity

  1. Jammy2211 commented on Aug 10, 2026

    @Jammy2211
    ContributorAuthor

    Correction to defect 1, after measuring it

    This issue describes defect 1 as "pushes Mind straight to main, bypassing review". Measuring the old helper against real bare remotes shows that is only half of it, and not the half that fires most often.

    git push origin main pushes the local main ref — never the commit just made on a feature branch. So the old behaviour splits by the state of local main:

    local main vs remote what the old script did
    equal — the normal case, a session that branched from it push is a no-op, exits 0, and the committed work never leaves the machine. The caller is told the sync succeeded.
    ahead — unpushed commits on main publishes those to remote main, unreviewed, and still leaves the branch work local.

    The first row is the common one and the quieter danger: in an ephemeral cloud container, "committed, reported synced, never pushed" is silent loss. The second is the review bypass this issue originally described.

    Same root cause, and the fix already on PyAutoMind#178 (push HEAD) addresses both. What changed is the description and the coverage — the two halves now have their own tests, since they are distinct failures:

    • test_committed_work_actually_reaches_the_remote
    • test_a_branch_push_never_advances_main

    5 of the 7 new tests now fail against origin/main's script.

    Also found in the same audit (already fixed)

    Auditing PyAutoBrain#224 against main turned up an unrelated regression in that PR: widening the band column to fit the new needs-review label had changed the printed output of the default path too, adding six columns of indent to runs that can never emit that band. The suspects were unaffected — verified identical against main, 29 of 135, no result keys removed — but the text a human reads had shifted for work unrelated to the feature. Fixed in c7c8b17: the width now follows the widest band actually present, so a default run prints exactly what main printed.

    Confirmed NOT broken by defect 2's fix

    worktree_claim_is_stale in the same file is documented fail-open by design ("anything unknown … returns 1 = not stale and the caller proceeds"). Making worktree_check_conflict fail closed does not contradict it: that function resolves active.md itself and never calls worktree_list_claimed, so its behaviour is untouched. Both functions fail toward safety — for staleness the safe answer is "don't remove", for conflicts it is "don't start".


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions