fix: throwaway repros silently used the live session - #2600
Conversation
📝 WalkthroughWalkthroughThe throwaway reproduction workflow now starts an isolated headless server session, creates its initial workspace, targets commands explicitly, supports optional attached clients, and verifies cleanup. Related Herdr guidance and the changelog describe the isolation behavior. ChangesThrowaway session isolation
Estimated code review effort: 1 (Trivial) | ~5 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 8731fef1-46d1-4e5a-a8fe-e1a3dc923c35
📒 Files selected for processing (3)
.agents/skills/herdr-throwaway-repro/SKILL.mddocs/next/CHANGELOG.mdskills/herdr/SKILL.md
87141db to
8e86978
Compare
Greptile SummaryThe PR revises the throwaway-reproduction skill to explicitly target an isolated named headless server and documents session-selection safeguards.
Confidence Score: 4/5The PR is not yet safe to merge because the documented startup command still blocks the driving shell and prevents the reproduction workflow from continuing. The attempted fix correctly selects headless server mode, but that mode runs synchronously and the skill supplies no concrete background invocation, leaving the previously reported workflow failure outstanding. Files Needing Attention: .agents/skills/herdr-throwaway-repro/SKILL.md
|
| Filename | Overview |
|---|---|
| .agents/skills/herdr-throwaway-repro/SKILL.md | Reworks disposable-session isolation comprehensively, but the attempted foreground-server fix still lacks a concrete nonblocking launch command. |
| skills/herdr/SKILL.md | Updates general safety guidance to distinguish the headless server command from the nested TUI form. |
| docs/next/CHANGELOG.md | Records the throwaway-reproduction isolation correction. |
Sequence Diagram
sequenceDiagram
participant Agent
participant Shell
participant Herdr as Headless server
Agent->>Shell: herdr --session name server
Shell->>Herdr: run_server()
Herdr-->>Shell: blocks until shutdown
Note over Agent,Shell: Readiness and workspace steps cannot execute
Agent-xShell: session list / workspace create
Reviews (3): Last reviewed commit: "fix: throwaway repros silently used the ..." | Re-trigger Greptile
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
.agents/skills/herdr-throwaway-repro/SKILL.md (1)
50-60: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winRun the server in the background before continuing.
The command ends with
herdr --session <session-name> server, but the server stays in the foreground. A shell or pane that follows these instructions blocks at this step, sosession list, workspace creation, and reproduction commands do not run.Use the installed tool's background primitive, or background the process and redirect its output to a
/var/tmplog.Proposed shell change
- herdr --session <session-name> server + nohup herdr --session <session-name> server \ + >"/var/tmp/<session-name>-server.log" 2>&1 </dev/null &
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 7daea657-a646-483a-bf63-c445b1a2ea86
📒 Files selected for processing (2)
.agents/skills/herdr-throwaway-repro/SKILL.mddocs/next/CHANGELOG.md
🚧 Files skipped from review as they are similar to previous changes (1)
- docs/next/CHANGELOG.md
8e86978 to
c7fc62a
Compare
The herdr-throwaway-repro skill did not have its intended effect. A disposable session never started, and once it failed there was nothing left to stop the reproduction from running in the user's live session. The skill's only documented way to create a disposable session was `herdr --session <name>` typed into an outer pane. That launches the TUI, and a TUI launched from a Herdr-managed pane exits 1 with "nested herdr is disabled by default" unless the user opted into `experimental.allow_nested`. The rejection was printed inside the scratch pane, which was closed later, while the driving shell only saw 25 seconds of "No such file or directory" from polling for a session socket that was never going to appear. Then it fell back to `herder pane send-text/send-keys/read` with no explicit session and no env prefix, which hit the default session. Fix by starting the disposable session with `herdr --session <name> server`, which is dispatched before the nested gate and needs no config change or outer pane. Address it with the `--session` flag, which marks the session explicit so the inherited HERDR_SOCKET_PATH is ignored and a missing session fails with server_not_running. The HERDR_SESSION variable the skill used for targeting does not do this; it loses to that inherited socket path and answers from the user's session instead.
c7fc62a to
7708d15
Compare
| ```bash | ||
| # To be run as a background job | ||
| env \ | ||
| -u HERDR_SOCKET_PATH \ | ||
| -u HERDR_CLIENT_SOCKET_PATH \ | ||
| -u HERDR_SESSION \ | ||
| -u HERDR_WORKSPACE_ID \ | ||
| -u HERDR_TAB_ID \ | ||
| -u HERDR_PANE_ID \ | ||
| herdr --session <session-name> | ||
| herdr --session <session-name> server | ||
| ``` |
There was a problem hiding this comment.
When an agent executes this startup block literally, herdr --session <session-name> server runs synchronously and occupies the shell until shutdown, so the readiness check, workspace creation, and remaining reproduction steps cannot execute. Referring to an unspecified harness background primitive does not provide an executable nonblocking launch.
There was a problem hiding this comment.
Actionable comments posted: 3
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: f247886a-071d-4de4-80a9-a208fdafce70
📒 Files selected for processing (2)
.agents/skills/herdr-throwaway-repro/SKILL.mddocs/next/CHANGELOG.md
🚧 Files skipped from review as they are similar to previous changes (1)
- docs/next/CHANGELOG.md
| env \ | ||
| -u HERDR_SOCKET_PATH \ | ||
| -u HERDR_CLIENT_SOCKET_PATH \ | ||
| -u HERDR_SESSION \ | ||
| -u HERDR_WORKSPACE_ID \ | ||
| -u HERDR_TAB_ID \ | ||
| -u HERDR_PANE_ID \ | ||
| herdr --session <session-name> | ||
| herdr --session <session-name> server |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Validate and quote the session name before shell interpolation.
The suggested repro-<topic>-<timestamp> format does not constrain <topic>. When the placeholder is replaced with whitespace, shell metacharacters, /, or .., the commands can split arguments, execute unintended commands, or escape /var/tmp. Require a name such as ^[A-Za-z0-9][A-Za-z0-9_-]*$, and quote every session-name expansion.
Proposed validation pattern
+case "$session_name" in
+ ''|*[!A-Za-z0-9_-]*) exit 1 ;;
+esac
+
- herdr --session <session-name> server
+ herdr --session "$session_name" server| ```bash | ||
| printf '[experimental]\nallow_nested = true\n' > /var/tmp/<session-name>-config.toml | ||
| ``` |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
Create the nested-client config with an exclusive temporary path.
printf ... > /var/tmp/<session-name>-config.toml can follow an existing symlink or overwrite an unrelated file. Cleanup can then remove that file. Use mktemp, store the returned path, pass that path through HERDR_CONFIG_PATH, and remove only that exact path.
Proposed temporary-file handling
-printf '[experimental]\nallow_nested = true\n' > /var/tmp/<session-name>-config.toml
+config_path="$(mktemp /var/tmp/herdr-repro-config.XXXXXX)"
+printf '[experimental]\nallow_nested = true\n' >"$config_path"
- HERDR_CONFIG_PATH=/var/tmp/<session-name>-config.toml
+ HERDR_CONFIG_PATH="$config_path"
- Remove /var/tmp/<session-name>-config.toml
+ Remove "$config_path"Also applies to: 166-167
| ## Address only the disposable session | ||
|
|
||
| Every control command issued from the parent must clear inherited socket overrides and explicitly select the temporary session: | ||
| Select the session with the `--session` flag on every control command: | ||
|
|
||
| ```bash | ||
| env \ | ||
| -u HERDR_SOCKET_PATH \ | ||
| -u HERDR_CLIENT_SOCKET_PATH \ | ||
| -u HERDR_WORKSPACE_ID \ | ||
| -u HERDR_TAB_ID \ | ||
| -u HERDR_PANE_ID \ | ||
| HERDR_SESSION=<session-name> \ | ||
| herdr pane list | ||
| herdr --session <session-name> pane list | ||
| ``` | ||
|
|
||
| Repeat this prefix for every command. Do not rely on shell state persisting between tool calls. | ||
| The flag marks the session explicit, so Herdr ignores the `HERDR_SOCKET_PATH` inherited from the surrounding pane. Naming a session that is not running then fails with `server_not_running` instead of answering from the user's session. | ||
|
|
||
| The `HERDR_SESSION` environment variable does not do this. Inside a Herdr pane `HERDR_SOCKET_PATH` already points at the user's server and takes precedence over that variable, so `HERDR_SESSION=<session-name> herdr pane list` reads and mutates the user's session and reports success. A bare `herdr pane list` does the same. Treat any command without `--session` as aimed at the user's session. | ||
|
|
||
| Repeat the flag on every server-scoped command. Do not rely on shell state persisting between tool calls. |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- inherited target identifiers ---'
rg -n -C 8 \
'HERDR_(WORKSPACE_ID|TAB_ID|PANE_ID)|--session' \
src tests docs || true
printf '%s\n' '--- target resolution ---'
rg -n -C 10 \
'resolve.*(workspace|tab|pane)|current.*(workspace|tab|pane)|default.*(workspace|tab|pane)' \
src tests || trueRepository: herdrdev/herdr
Length of output: 50371
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- relevant files ---'
git ls-files | rg '(^|/)(SKILL\.md|.*(cli|session|target|env).*)$' | head -200
printf '%s\n' '--- exact environment-variable references ---'
rg -n -S \
'HERDR_(WORKSPACE_ID|TAB_ID|PANE_ID|SESSION|SOCKET_PATH)|session' \
--glob '!src/ghostty/**' \
--glob '!target/**' \
--glob '!*.lock' \
.agents src tests docs 2>/dev/null | head -1000
printf '%s\n' '--- skill context ---'
cat -n .agents/skills/herdr-throwaway-repro/SKILL.md | sed -n '70,115p'Repository: herdrdev/herdr
Length of output: 50371
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- session selection and CLI setup ---'
cat -n src/session.rs | sed -n '1,190p'
cat -n src/cli.rs | sed -n '1,180p'
printf '%s\n' '--- target parsing and CLI command handlers ---'
cat -n src/app/terminal_targets.rs | sed -n '1,260p'
cat -n src/cli/pane.rs | sed -n '1,240p'
cat -n src/cli/workspace.rs | sed -n '1,180p'
cat -n src/cli/tab.rs | sed -n '1,180p'
printf '%s\n' '--- target environment names in current source ---'
rg -n -S \
'HERDR_(WORKSPACE_ID|TAB_ID|PANE_ID|ACTIVE_WORKSPACE_ID|ACTIVE_TAB_ID|ACTIVE_PANE_ID)' \
src tests .agents \
--glob '!src/ghostty/**' \
--glob '!vendor/**' || trueRepository: herdrdev/herdr
Length of output: 50370
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- all CLI uses of inherited caller IDs ---'
rg -n -C 6 \
'HERDR_(WORKSPACE_ID|TAB_ID|PANE_ID)|parse_optional_current|--current|caller_pane_id' \
src/cli/pane.rs src/cli/workspace.rs src/cli/tab.rs src/cli/runtime.rs src/cli.rs
printf '%s\n' '--- server-side current-target handling ---'
rg -n -C 10 \
'PaneCurrent|caller_pane_id|workspace_id.*caller|current.*pane|public_pane_id|public_workspace_id|public_tab_id' \
src/api src/app src/server \
--glob '!**/ghostty/**' | head -1200
printf '%s\n' '--- ID generation and environment injection ---'
cat -n src/integration/env.rs | sed -n '1,180p'
cat -n src/pane.rs | sed -n '110,165p'
rg -n -C 8 \
'fn (public_pane_id|public_workspace_id|public_tab_id)|public_pane_id\(' \
src/app src | head -500Repository: herdrdev/herdr
Length of output: 50371
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- exact target parser implementations ---'
cat -n src/cli/pane.rs | sed -n '227,295p;544,590p;623,680p;900,990p'
cat -n src/cli/runtime.rs | sed -n '1,180p'
printf '%s\n' '--- workspace and public-ID construction ---'
rg -n -C 8 \
'pub fn (public_(workspace|tab|pane)_id_for_number)|public_(workspace|tab|pane)_id_for_number|struct Workspace|fn test_new|WorkspaceId' \
src/workspace.rs src/app/ids.rs src | head -800
printf '%s\n' '--- focused static behavioral verifier ---'
python3 - <<'PY'
from pathlib import Path
session = Path("src/session.rs").read_text()
pane = Path("src/cli/pane.rs").read_text()
assert 'apply_explicit_name(&session)?;' in session
assert 'std::env::remove_var("HERDR_PANE_ID")' not in session
assert 'std::env::remove_var("HERDR_WORKSPACE_ID")' not in session
assert 'std::env::remove_var("HERDR_TAB_ID")' not in session
assert 'std::env::var("HERDR_PANE_ID")' in pane
assert '--current' in pane
print("explicit --session selection does not clear inherited target IDs")
print("HERDR_PANE_ID remains an input to pane current-target parsing")
PYRepository: herdrdev/herdr
Length of output: 50370
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- workspace ID generation and cross-session uniqueness ---'
cat -n src/workspace.rs | sed -n '90,170p;1260,1315p'
rg -n -C 8 \
'generate_workspace_id|NEXT_WORKSPACE|Atomic.*workspace|workspace.*id' \
src/workspace.rs src | head -500
printf '%s\n' '--- API fallback for omitted and explicit pane targets ---'
rg -n -C 12 \
'PaneLayout|PaneSplit|PaneInputSet|PaneCurrent|params\.pane_id|pane_id\.as|unwrap_or.*focused|focused.*pane' \
src/api/server.rs src/server/headless.rs src/app | head -1200
printf '%s\n' '--- focused behavioral source verifier ---'
python3 - <<'PY'
from pathlib import Path
session = Path("src/session.rs").read_text()
pane = Path("src/cli/pane.rs").read_text()
ids = Path("src/workspace.rs").read_text()
checks = {
"explicit session does not clear caller IDs": all(
f'remove_var("HERDR_{name}")' not in session
for name in ("WORKSPACE_ID", "TAB_ID", "PANE_ID")
),
"pane current reads HERDR_PANE_ID": 'std::env::var("HERDR_PANE_ID")' in pane
and 'caller_pane_id = env_pane_id.map' in pane,
"pane split current reads HERDR_PANE_ID": 'ok_or("--current requires HERDR_PANE_ID")' in pane,
"public pane IDs include workspace IDs": 'format!("{workspace_id}:p' in ids,
}
for label, result in checks.items():
print(f"{label}: {result}")
assert all(checks.values())
PYRepository: herdrdev/herdr
Length of output: 50371
Clear inherited caller IDs for disposable-session commands that use current targets.
--session selects the socket but does not clear HERDR_PANE_ID. Current-target selectors can resolve a parent-session ID to a different disposable-session pane. Run these commands with env -u HERDR_WORKSPACE_ID -u HERDR_TAB_ID -u HERDR_PANE_ID, or use explicit IDs returned by the disposable session.
The herdr-throwaway-repro skill did not have its intended effect. A disposable session never started, and once it failed there was nothing left to stop the reproduction from running in the user's live session.
The skill's only documented way to create a disposable session was
herdr --session <name>typed into an outer pane. That launches the TUI, and a TUI launched from a Herdr-managed pane exits 1 with "nested herdr is disabled by default" unless the user opted intoexperimental.allow_nested.The rejection was printed inside the scratch pane, which was closed later, while the driving shell only saw 25 seconds of "No such file or directory" from polling for a session socket that was never going to appear. Then it fell back to
herdr pane send-text/send-keys/readwith no explicit session and no env prefix, which hit the default session.Fix by starting the disposable session with
herdr --session <name> server, which is dispatched before the nested gate and needs no config change or outer pane. Address it with the--sessionflag, which marks the session explicit so the inherited HERDR_SOCKET_PATH is ignored and a missing session fails with server_not_running. The HERDR_SESSION variable the skill used for targeting does not do this; it loses to that inherited socket path and answers from the user's session instead.