Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,29 @@
workflow_dispatch:

jobs:
# Keep the process launch contract independently visible while the full
# lane-helper suite runs. Each GitHub job has its own workstation runner;
# the canonical wrapper still serializes any pytest processes on that runner.
guard-launch-mode:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
submodules: true
fetch-depth: 1
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: '3.x'
- name: install the test runner
run: python3 -m pip install --disable-pip-version-check pytest==8.3.4

Check warning on line 41 in .github/workflows/tests.yml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Omitting "--only-binary :all:" can lead to the execution of setup scripts. Make sure it is safe here.

See more on https://sonarcloud.io/project/issues?id=opensoft_openRepoTools&issues=AaDzlk3accIokdjPw_UA&open=AaDzlk3accIokdjPw_UA&pullRequest=135
- name: guard launch mode and repository hygiene
env:
GIT_AUTHOR_NAME: openRepoTools CI
GIT_AUTHOR_EMAIL: ci@openrepotools.invalid
GIT_COMMITTER_NAME: openRepoTools CI
GIT_COMMITTER_EMAIL: ci@openrepotools.invalid
run: bash tests/run.sh -k 'guard_launch_mode or repo_hygiene'

tests:
runs-on: ubuntu-latest
steps:
Expand Down
16 changes: 8 additions & 8 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@ amendments — and `docs/README-lanes.md` is its manual. **The code is here; the
data is not.** Every one of the four finds the register, the logs and the
handoffs through `$AGENT_PROTOCOL_ROOT/workspace.yaml`'s `repository:` and
`path:`, never from its own location on disk, and refuses with exit 1 naming
`openRepoTools wip init` where that file does not answer — with ONE exception,
and it is a mechanism and not a taste: `lanes-edit.sh guard`, the
`UserPromptSubmit` name guard Amendment 12 adds, answers that same unreadable
workspace with **2**, because only a 2 blocks a prompt and a 1 would print the
refusal and let the work through it. Their exit codes are
the protocol's and not this toolset's — 1 is *registry not found* there and
*findings were printed* here — so never read a number without knowing which
command produced it.
`openRepoTools wip init` where that file does not answer. For lane sessions,
`lanes-edit.sh guard`, Amendment 12's `UserPromptSubmit` name guard, answers an
unreadable workspace with **2**: only 2 blocks a prompt; 1 would let it through.
Profile-only guard calls carrying exact `CLAUDE_NO_LANE=1` return 0 before input
or identity probes, even with unreadable configuration; other values enforce.
Brett Heap directed this exception in `openspec/changes/skip-no-lane-guard/`;
Shared hooks stay installed. Exit 1 means *registry not found* in lane tooling,
and *findings were printed* in estate status; identify the command before interpreting it.

## Driving `park <Name>`, `resume <Name>` and `status <Name>`

Expand Down
14 changes: 11 additions & 3 deletions docs/README-lanes.md
Original file line number Diff line number Diff line change
Expand Up @@ -1862,7 +1862,7 @@ because a hook that fails is a hook that breaks the session it was meant to
orient (R-A8-1). Where another live process carries this session id it **SAYS
so** and names the retire act; the refusal is the guard's.

### Fail closed, and the one bypass
### Fail closed for lane sessions, and safe-mode recovery

A mismatch refuses, and so does an **indeterminate read** — session records
unreadable, tmux not answering, the register unreadable, a payload naming no
Expand All @@ -1872,15 +1872,23 @@ checkout last had it (R19) and **the guard never fetches**: this hook runs at
every prompt, and putting the network there would be R-A8-1's objection several
times over.

**There is no environment flag that turns it off.** `claude --safe-mode` runs
with every hook disabled and is the one bypass — deliberate, visible in the
Profile-only launches with exact `CLAUDE_NO_LANE=1` are exempt, as described
under Scope below. For a session subject to the guard, `claude --safe-mode` runs
with every hook disabled — deliberate, visible in the
prompt box, and **a session started that way is not a lane session**: it may not
write the register or claim an object. The hook's `timeout 5` is the other way
it can fail open, and it is the amendment's own number: a hook that exceeds its
timeout is killed, and a killed hook does not exit 2.

### Scope

Sessions launched with `CLAUDE_NO_LANE=1` skip the prompt guard, silently. The
workBenches profile launcher supplies this marker for a launch without a lane
and clears it for an explicit lane launch. Explicit `--no-lane` wins over lane
options. Other marker values keep the normal checks. The hook stays installed
in profile settings so a concurrent lane session using the same profile still
receives enforcement. Existing sessions need a relaunch to change launch mode.
Comment thread
brettheap marked this conversation as resolved.

The guard applies to every session whose `cwd` is under `$PROJECTS_ROOT`
(default `~/projects`), which is every estate session, and is **silent
elsewhere** (D1): a name there is a title and nothing more. A **subagent's**
Expand Down
47 changes: 27 additions & 20 deletions lanes-edit.sh
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,26 @@

set -u

# Parse the global flags before probing paths, git or workstation identity.
# A profile-only prompt must not depend on any of that lane infrastructure.
SWEEP_MODE="no-sweep"
while [ $# -gt 0 ]; do
case "${1-}" in
--no-sweep) SWEEP_MODE="no-sweep"; shift ;;
--sweep) SWEEP_MODE="sweep"; shift ;;
--no-github) LANES_NO_GITHUB=1; shift ;;
*) break ;;
esac
done

if [ "${1-}" = guard ] && [ "${CLAUDE_NO_LANE:-}" = 1 ]; then
if [ "$#" -ne 1 ]; then
printf '%s\n' "lanes-edit: usage: guard (the UserPromptSubmit hook; the hook's JSON on stdin)" >&2
exit 2
fi
exit 0
fi

SELF="$0"
if command -v readlink >/dev/null 2>&1; then
RESOLVED="$(readlink -f "$SELF" 2>/dev/null || printf '%s' "$SELF")"
Expand Down Expand Up @@ -711,19 +731,6 @@ LANES_PATH="${LANES_PATH:-$(git -C "${LANES_DIR:-.}" rev-parse --show-prefix 2>/
CP_PATHS=("$LANES_PATH")
CP_AFTER_REBASE=""

# --no-sweep (default) / --sweep — see USAGE above. Consumed here, ahead of
# subcommand dispatch, so either flag may appear anywhere before the
# subcommand name.
SWEEP_MODE="no-sweep"
while [ $# -gt 0 ]; do
case "${1-}" in
--no-sweep) SWEEP_MODE="no-sweep"; shift ;;
--sweep) SWEEP_MODE="sweep"; shift ;;
--no-github) LANES_NO_GITHUB=1; shift ;;
*) break ;;
esac
done

# die "<message>" [<exit code>] — the MESSAGE is $1 alone. It used to be "$*",
# which joined the exit code on to the end of every refusal that passed one:
# `… is not a stale claim. 2`. The code is the caller's business and the
Expand Down Expand Up @@ -6307,10 +6314,10 @@ EOF
# FAIL CLOSED (clause (d)). A mismatch refuses; so does an INDETERMINATE read —
# session records unreadable, tmux not answering, the register unreadable —
# naming the read, because a triple that cannot be verified is not a triple that
# agrees. There is no environment flag that turns this off: `claude --safe-mode`
# disables every hook and is the one bypass, deliberate and visible in the
# prompt box, and a session started that way is not a lane session and may not
# write the register or claim an object.
# agrees. Profile-only launches carrying exactly `CLAUDE_NO_LANE=1` are exempt
# from this guard. `claude --safe-mode` disables every hook, deliberate and
# visible in the prompt box; a session started that way is not a lane session
# and may not write the register or claim an object.
#
# WHAT IT COSTS, AND THE ONE WAY IT FAILS OPEN. Measured on Eagle 2026-09-14
# against 329 session records and a 1.3 MB register: the register read is ~2 s
Expand Down Expand Up @@ -6734,9 +6741,9 @@ guard_triple() { # [<heading>]
return 0
}

# THE ONE BYPASS, NAMED WHERE A PERSON COULD OTHERWISE BE STUCK (clause (d)).
# There is no environment flag that turns this guard off, deliberately; Claude's
# own `--safe-mode` disables every hook and is visible in the prompt box, and a
# SAFE-MODE RECOVERY, NAMED WHERE A PERSON COULD BE STUCK (clause (d)).
# Profile-only launches are exempt through `CLAUDE_NO_LANE=1` at dispatch.
# Claude's `--safe-mode` disables every hook and is visible in the prompt box; a
# session started that way is not a lane session and may not write the register
# or claim an object. It is printed by the INDETERMINATE refusals — the reads
# that could not be made — and not by the mismatches, which have a cure of their
Expand Down
2 changes: 2 additions & 0 deletions openspec/changes/skip-no-lane-guard/.openspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-09-30
19 changes: 19 additions & 0 deletions openspec/changes/skip-no-lane-guard/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
## Context

See proposal.md. workBenches selects profile and lane modes per launch; profile settings can be used concurrently by both modes.

## Goals / Non-Goals

Honor the process's no-lane marker without editing shared profile settings. Other lane checks and commands retain their existing behavior.

## Decisions

The `guard` handler exits silently with status 0 for the exact environment value `CLAUDE_NO_LANE=1`, before reading hook input or validating lane identity. All other values use the existing enforcement path. Changing profile settings would affect concurrent lane sessions, so the exemption belongs to the child process environment.

## Risks / Trade-offs

An inherited marker could exempt a later lane launch. workBenches clears it for explicit lane requests and lane handoff; only explicit `--no-lane` wins over those requests. Existing Claude processes keep their launch environment and must be relaunched to change mode.

## Migration Plan

Install the marker-aware guard with the normal openRepoTools installation path and install the matching workBenches launcher. Revert both changes to roll back. Do not edit the pinned workBenches vendor copy directly.
22 changes: 22 additions & 0 deletions openspec/changes/skip-no-lane-guard/proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
## Why

Profile-only Claude launches currently encounter the lane name guard even though the operator chose to work without a lane. The guard must honor that launch mode for the lifetime of the process.

## What Changes

- Skip the prompt guard when the launcher supplies `CLAUDE_NO_LANE=1`.
- Keep the hook installed and enforce existing checks for every other value.
- Document the process environment contract with workBenches.
- Run the expanded guard and repository hygiene checks in a focused CI job using the canonical serialized wrapper.

## Capabilities

### New Capabilities

- `profile-only-lane-guard`: per-session exemption for a launch without a lane.

### Modified Capabilities

## Impact

`lanes-edit.sh guard`, lane documentation and regression coverage. workBenches supplies the marker. Implementation handoff: `specs/002-skip-no-lane-guard/`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
## Purpose

Allow Claude sessions launched without a lane to submit prompts while lane sessions retain their normal identity checks.

## ADDED Requirements

### Requirement: Profile-only prompts avoid lane initialization

The guard SHALL apply the exact launch exemption before probing workspace
configuration, git, workstation identity or its own command path.

#### Scenario: Infrastructure probes are unavailable

- **WHEN** a marked guard invocation would encounter unavailable identity commands
- **THEN** it exits silently with status 0 without invoking those commands

#### Scenario: Invalid guard arguments accompany the marker

- **WHEN** a marked guard invocation includes unexpected arguments
- **THEN** it prints usage and returns status 2 without identity probes

### Requirement: Respect the session launch mode

The prompt guard SHALL silently return status 0 when `CLAUDE_NO_LANE=1`, including when lane identity cannot be read. It SHALL keep existing enforcement for all other values and SHALL NOT alter profile hook settings or lane records for the exempt prompt.

#### Scenario: Profile session inside the projects root

- **WHEN** a prompt arrives from a process launched with `CLAUDE_NO_LANE=1` inside the projects root
- **THEN** it is allowed without consulting tmux or requiring a lane row

#### Scenario: Unsupported marker value

- **WHEN** the marker is absent, empty, `0`, or `true` and no lane workspace can be read
- **THEN** the guard returns status 2 through its existing refusal path

### Requirement: Limit the exemption to the prompt guard

Other lane commands SHALL retain their existing validation even when `CLAUDE_NO_LANE=1` is present.

#### Scenario: Registry read with no workspace

- **WHEN** a registry read runs with `CLAUDE_NO_LANE=1` and no workspace configuration
- **THEN** it still refuses the missing workspace
5 changes: 5 additions & 0 deletions openspec/changes/skip-no-lane-guard/tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
## 1. Speckit handoff

- [x] 1.1 Record the authorized behavior and hand off implementation to `specs/002-skip-no-lane-guard/tasks.md`; verify the spec, plan and task files exist.

Implementation tracking is owned by that Speckit task file.
7 changes: 7 additions & 0 deletions specs/002-skip-no-lane-guard/plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Implementation Plan

Governance: `openspec/changes/skip-no-lane-guard/`.

Add an exact-value early exit before script initialization using Bash 3.2 syntax, preserving global option parsing and status-2 argument validation. Update the scope documentation and repository contract. Add subprocess regression coverage against isolated home/config directories and sentinels for tmux, git, hostname, readlink and workspace parsing. Verify with `tests/run.sh -k guard_launch_mode`, Bash syntax checks and `openspec validate skip-no-lane-guard --strict`.

Dependency: workBenches feature `017-export-no-lane-mode` supplies and clears the process marker. Installed estate commands and vendored pins use their existing release paths.
30 changes: 30 additions & 0 deletions specs/002-skip-no-lane-guard/spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Feature Specification: Skip the guard for no-lane sessions

**Feature Branch**: `002-skip-no-lane-guard`
**Created**: 2026-09-30
**Status**: Approved for implementation by the user's request
**Input**: Implement the agreed per-session no-lane hook exemption.

## User Scenarios & Testing

### User Story 1 - Work without a lane (Priority: P1)

A profile session submits prompts even inside a projects directory with no tmux or readable lane workspace. Test the guard with the launch marker and missing lane infrastructure.

### User Story 2 - Keep lane enforcement (Priority: P1)

A lane session must still satisfy the existing identity checks. Test absent and unsupported marker values against the same missing-workspace fixture and expect status 2.

## Requirements

- **FR-001**: Exact `CLAUDE_NO_LANE=1` bypasses only the prompt guard, silently and before reading hook input.
- **FR-002**: Absent or unsupported values retain the existing checks.
- **FR-003**: Exempt prompts cause no lane record or tmux mutation.
- **FR-004**: Keep shared profile hooks installed.
- **FR-005**: Marked guard invocations avoid workspace, git, workstation and command-path probes, including when global flags are present; invalid guard arguments still return status 2.

## Success Criteria

- Exempt prompt fixtures return status 0 with empty stdout and stderr.
- Non-exempt fixtures refuse unreadable lane identity with status 2.
- Other lane commands still refuse missing workspace configuration.
12 changes: 12 additions & 0 deletions specs/002-skip-no-lane-guard/tasks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Tasks

- [x] T001 Add the guard exemption and document its scope.
- [x] T002 Add regression cases for exact marker handling, unreadable identity and unaffected non-guard commands.
- [x] T003 Run focused tests, syntax checks and governance validation; record results.

## Verification

- The canonical focused CI command `bash tests/run.sh -k 'guard_launch_mode or repo_hygiene'` passed 165 checks, with 528 deselected, on implementation commit `64f4e93`. This includes all 13 expanded launch-mode cases and proves marked prompts do not invoke identity probes.
- The launcher/guard end-to-end smoke check passed: a profile-only launch exports the marker and permits its prompt, while an unmarked guard still refuses unreadable identity with status 2.
- Changed shell files pass `bash -n`; diff whitespace checks and strict OpenSpec validation pass.
- Windows checks and macOS Bash 3.2 syntax parsing passed. The focused CI runner honors the same wrapper and serialization rules as local runs.
Loading
Loading