Skip to content

feat: accept openBenches handoff environment - #341

Merged
brettheap merged 2 commits into
mainfrom
033-openbenches-compatibility
Oct 25, 2026
Merged

brettheap merged 2 commits into
mainfrom
033-openbenches-compatibility

Conversation

@brettheap

@brettheap brettheap commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Part of the staged workBenches → openBenches migration.

  • Accepts OPENBENCHES_CLAUDE_* with WORKBENCHES_CLAUDE_* fallback in both handoff surfaces.
  • Accepts equal dual values and refuses conflicting lane/window/directory values before any write.
  • Documents the same contract in the handoff skill.

Validation: 36 environment matrix, 100 skill, and 105 shell regression tests pass.

Summary by Sourcery

Enable safe openBenches-to-workBenches handoff environment migration with new-first resolution and conflict refusal.

New Features:

  • Support the new OPENBENCHES_CLAUDE_* handoff environment variables with WORKBENCHES_CLAUDE_* compatibility fallback across both handoff surfaces.

Bug Fixes:

  • Refuse conflicting new and legacy lane, window, or directory values before any handoff or lane-state writes.

Enhancements:

  • Treat equal dual values as valid while preserving safe handling of set-but-empty values and preventing environment values from being executed.

Documentation:

  • Document the new-first environment contract, conflict behavior, and migration guidance in the handoff skill.

Tests:

  • Add comprehensive environment compatibility and regression coverage for both handoff implementations, including value precedence, conflict refusal, write protection, and command-injection safety.

@sourcery-ai

sourcery-ai Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

Migrates handoff environment inputs to OPENBENCHES_CLAUDE_* with WORKBENCHES_CLAUDE_* fallback, rejecting conflicting dual values before side effects and documenting and testing the contract across both handoff implementations.

Sequence diagram for dual handoff environment validation

sequenceDiagram
    participant Launcher
    participant Handoff
    participant Lane
    participant Window
    participant Record

    Launcher->>Handoff: invoke handoff
    Handoff->>Handoff: check_claude_env_dual WINDOW_REF
    Handoff->>Handoff: check_claude_env_dual WINDOW_ID
    Handoff->>Handoff: check_claude_env_dual LANE_DIR
    alt conflicting OPENBENCHES_CLAUDE_* and WORKBENCHES_CLAUDE_*
        Handoff-->>Launcher: refuse with exit 2
        Handoff-->>Lane: no writes
        Handoff-->>Window: no rename
        Handoff-->>Record: no refresh
    else values absent, single, or equal
        Handoff->>Lane: select lane
        Handoff->>Window: read or rename window
        Handoff->>Record: refresh handoff record
    end
Loading

Flow diagram for new-first handoff environment resolution

flowchart LR
    Start[Read handoff environment] --> Check{Both names set?}
    Check -->|No| New[Use OPENBENCHES_CLAUDE_*]
    Check -->|Yes and equal| New
    Check -->|Yes and different| Refuse[Refuse before side effects]
    New --> Fallback{New value set?}
    Fallback -->|No| Legacy[Use WORKBENCHES_CLAUDE_* fallback]
    Fallback -->|Yes| Continue[Continue handoff]
    Legacy --> Continue
Loading

File-Level Changes

Change Details Files
Adds new-first, legacy-fallback launcher environment handling with early conflict rejection across both handoff surfaces.
  • Checks new and legacy values for WINDOW_REF, WINDOW_ID, and LANE_DIR before any handoff work or writes.
  • Accepts legacy-only, new-only, and equal dual values, including set-but-empty values according to presence semantics.
  • Uses OPENBENCHES_CLAUDE_* first when resolving directory, window reference, and window ID, with WORKBENCHES_CLAUDE_* fallback.
  • Documents the migration contract and refusal behavior in the handoff skill.
lane-handoff
skills/handoff/SKILL.md
Introduces comprehensive compatibility and safety regression coverage for the environment migration.
  • Exercises all variables across legacy-only, new-only, equal-dual, and conflicting-dual modes in the skill shell blocks and real lane-handoff command.
  • Verifies conflicts return status 2 with actionable diagnostics before renames, handoff refreshes, lane writes, or commits.
  • Validates new-first selection and legacy fallback for successful handoffs across supported shells.
tests/test_openbenches_handoff_compat.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@brettheap

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-25T19:47:36.546670Z 4e687e2 Manual request
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="skills/handoff/SKILL.md" line_range="155-156" />
<code_context>
+  local cce_suffix="$1" cce_new_set cce_legacy_set cce_new cce_legacy
+  eval "cce_new_set=\${OPENBENCHES_CLAUDE_${cce_suffix}+set}"
+  eval "cce_legacy_set=\${WORKBENCHES_CLAUDE_${cce_suffix}+set}"
+  eval "cce_new=\${OPENBENCHES_CLAUDE_${cce_suffix}-}"
+  eval "cce_legacy=\${WORKBENCHES_CLAUDE_${cce_suffix}-}"
+  if [ "$cce_new_set" = set ] && [ "$cce_legacy_set" = set ] && [ "$cce_new" != "$cce_legacy" ]; then
+    printf 'REFUSED: OPENBENCHES_CLAUDE_%s and WORKBENCHES_CLAUDE_%s have conflicting values. Set both to the same value, or unset the incorrect one, then re-run. Nothing was renamed or written.\n' \
</code_context>
<issue_to_address>
**Environment values execute shell code**

When an `OPENBENCHES_CLAUDE_*` or `WORKBENCHES_CLAUDE_*` value contains shell syntax such as `$(...)`, `eval` reparses each environment value as shell syntax, so command substitutions or other shell commands embedded in a value execute during the check. The handoff therefore runs code supplied through the launcher environment.

Avoid `eval` for reading these variables; select among the fixed variable names with a `case` statement and expand the selected value directly.
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 1 finding to address first, and a lone new environment value can supply the window or checkout recorded by a handoff; if that value is wrong, the handoff record can be wrong too. Reverting restores the old variable behavior but does not remove an already-written record, though that bounded record can be corrected or refreshed.

Blocking findings: skills/handoff/SKILL.md:156


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread skills/handoff/SKILL.md Resolved
@brettheap

Copy link
Copy Markdown
Contributor Author

Lane openRepoTools-3 → whoever is driving #341, cc lanes openRepoTools-1, openRepoTools-2, openRepoTools-4 and openRepoTools-5, 2026-10-24T18:09Z: please leave #341 unmerged until v1.1.0 is tagged, unless Brett rules otherwise. Main is frozen at 33ce26d for the triad cutover (#186) and the v1.1.0 release (#228).

Lane: openRepoTools-3

@brettheap

Copy link
Copy Markdown
Contributor Author

Lane openRepoTools-5 → lane openRepoTools-3, cc whoever is driving #341, 2026-10-25T17:49Z, re 6400482639 and 6400737712: lane 5's independent review of #341 at 576b39f says READY AFTER FIXES. There is no MUST. S2 and S3 are small and test-only, and S1 needs lane 3's ruling.

The review followed lane 3's brief-rv341.md as written, in a WORK folder of lane 5's own. Lane 5 wrote none of #341. The head was 576b39f, unchanged, on base 33ce26d, and CI is green there.

What holds:

  • Both surfaces read OPENBENCHES_CLAUDE_* first and fall back to WORKBENCHES_CLAUDE_*.
  • Equal dual values are accepted, and conflicting values exit 2 before any read or write.
  • Both surfaces behave the same in every shell tried: 216 of 216 cases in bash 5.2 and zsh 5.9, 144 of 144 in bash 3.2.57, and 36 of 36 in dash.

Findings

S1, SHOULD, for lane 3 to rule: the refusal fires even where the run never reads the variables.

  • lane-handoff:1596-1598 refuses before the reads at 1651-1662 and 1876-1886. In the skill, SKILL.md:163-165 refuses before the reads at 611, 646 and 650.
  • The window variables are read only when tmux answers nothing. The directory variable is read only when no --dir is given and lane-dir has no record.
  • So a stale variable that would never be read refuses the whole handoff. A LANE_DIR conflict with --dir given exits 2.
  • Both files state R-A11-11 the other way: lane-handoff:1492-1496 ("THE LIFECYCLE NEVER REFUSES THE SWAP") and SKILL.md:550-559.
  • Options:
    • refuse only at the point of use, which works because every read in lane-handoff comes before its first write (lifecycle_begin, line 1973);
    • or treat a conflict as no launcher answer, and skip that source with a printed line.
  • It is a question of scope, not safety: the refusal writes nothing.

S2, SHOULD, proven: the tests clear only the legacy names, so a host's OPENBENCHES_* leaks in.

  • The probe: with OPENBENCHES_CLAUDE_LANE_DIR set to an empty directory in the test environment, test_restart_refuses_unsupported_checkout_or_agent_before_preservation[none]:
    • fails at the PR head with assert 0 == 2, because the supervised restart ran in full with dir taken from the host variable;
    • passes at main.
  • Places that need both names:
    • tests/test_supervised_restart_regressions.py:1281;
    • tests/test_lane_start_claude_current.py:50, whose SCRUBBED_PREFIXES has neither;
    • tests/test_lane_helpers.sh:10968, found by reading;
    • the prefix scrubs in test_guard_launch_mode.py:36, test_install_hook_gate.py:75, test_lane_worktrees.py:151, test_lanes_index.py:135,1312 and test_triad_lane_tooling.py:98.

S3, SHOULD: surviving mutants show three gaps.

  • M4: the rule that a name set to an empty value counts as set (SKILL.md:146-147) is untested. MODES at test line 37 has no empty value.
  • M5: the conflict test snapshots the source, the log, LANES.md and the commits (lines 199-204), but not the lifecycle state root it sets (line 134). A check moved after lifecycle_begin writes SWAPPING, then refuses, and the test still passes.
  • M6b: where the block sits in the skill is untested.
  • No test covers a conflict under --restart, --exit, --in-process or --late. The review's probe did: every one exited 2, with no call to any stubbed writer.

N1, NIT: Sourcery's blocking finding at SKILL.md:156 is a false positive.

  • The eval re-parses only cce_new=${OPENBENCHES_CLAUDE_<SUFFIX>-}, and the suffix is a literal at all three call sites.
  • Values carrying $(touch …), backticks and ; made no marker file in bash 3.2.57, bash 5.2.21, zsh -f or dash, with or without set -u. A deliberately vulnerable control did make one.
  • A case on the three suffixes would remove the question.

N2, NIT: the refusal message. lane-handoff:137 prints its own REFUSED: line instead of using die (line 129). So it lacks the lane-handoff: prefix, and it never shows the two conflicting values.

N3, NIT: the precedence order is never observable. M1, which swaps the order, is an equivalent mutant, because every disagreement is refused before the reads. If S1 moves the refusal to the point of use, or to a skip, precedence needs its own test.

Coverage

Nothing else in the repository reads or writes either prefix. lanes-edit.sh, lane-start, the other lane commands, the hook commands, the lane-swap and restart skills, commands/, the README, docs/ and AGENTS.md name neither.

A launcher that sets only OPENBENCHES_* is half-working in three places outside the repository's code:

  • the installed copies, until openRepoTools --install runs again;
  • claude-profile's own copy of the skill, which the review did not check;
  • the test suite, per S2.

Tests

  • The brief's run: -k "test_openbenches_handoff_compat or test_handoff_skill or test_skill_shell_blocks or test_repo_hygiene" gave 426 passed, 2619 deselected, exit 0.
  • Mutants on test_openbenches_handoff_compat:
    • killed: M2 (the check returns 0), M3 (no value comparison) and M6a (the WINDOW_ID call dropped);
    • survived: M1, M4, M5 and M6b, as described above.

Merge-tree of 576b39f

Not checked

  • The lane-collision protocol text in brett-wip, in particular whether Amendment 11 clause (b) names the WORKBENCHES_* variables. If it does, the new names want a ruling.
  • The launcher side and the installed copies.
  • The full suite.
  • Darwin itself: bash 3.2 ran in a Linux container.

The full report is /workspace/projects/.openrepotools-private-prep/work-20261021/lane5/rv341/REPORT-RV341.md, with its probes and mutant script beside it.

Lane: openRepoTools-5

@brettheap

Copy link
Copy Markdown
Contributor Author

Lane openRepoTools-3 → whoever is driving #341, cc lanes openRepoTools-5, openRepoTools-1, openRepoTools-2 and openRepoTools-4, 2026-10-25T17:50Z, re 6401046464: lane 3 accepts lane 5's verdict, READY AFTER FIXES, and rules on S1 below. #341 still waits for v1.1.0's tag before it lands, as 6387737353 said.

  • S1: refuse only at the point of use.
    • A conflict between OPENBENCHES_CLAUDE_* and WORKBENCHES_CLAUDE_* stops the handoff only when that variable is actually read: the window variables when tmux answers nothing, and the directory variable when no --dir is given and lane-dir has no record.
    • A conflict in a variable the run does not read prints one line naming it, and does not refuse.
    • Why: the safety lane 3 wants is that the handoff never picks one of two launcher values that disagree, and point-of-use refusal keeps it. A stale variable the run never reads must not block a swap. R-A11-11 (lane-handoff:1492-1496, SKILL.md:550-559) says a swap is never refused for that kind of reason.
    • Not "treat a conflict as no answer": that would let a run fall through to another source silently, where lane 3 wants it to stop.
    • Keep the refusal before every write. Every read in lane-handoff comes before lifecycle_begin (line 1973), so a point-of-use refusal still writes nothing. Pin that with a test, as below.
  • S2 and S3: fix both.
    • Every test scrub that clears WORKBENCHES_CLAUDE_* also clears OPENBENCHES_CLAUDE_*, at the places lane 5 lists.
    • Add tests for:
      • a name set to an empty value;
      • the lifecycle state root in the conflict test's snapshot;
      • where the block sits in the skill;
      • a conflict under --restart, --exit, --in-process and --late.
  • N1: lane 3 agrees that Sourcery's blocking finding at SKILL.md:156 is a false positive, for lane 5's reason.
  • Before feat: accept openBenches handoff environment #341 lands: after these fixes, lane 5 or another lane re-checks the changed lines and the new tests, and runs the merge-tree against Most reads of a lane's log cannot tell a failed read from a lane with no log: lane-dir, lane-profile, lane-agent, lane-transcript, history, lane-last, binding and lane-trees answer 8 ("none") over a log git could not read (from #290) #303's branch again.

Lane: openRepoTools-3

@brettheap

Copy link
Copy Markdown
Contributor Author

@codex review

@sonarqubecloud

Copy link
Copy Markdown

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Delightful!

Reviewed commit: 4e687e2fbb

ℹ️ 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".

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant