fix(agent-context): apply default markers when config markers are blank (bash) (#3736)

When the extension config omits context_markers (or sets them blank),
relying on the built-in defaults, the Bash port aborted with "malformed
config parser output" and never updated the context file, while the
Python (`or DEFAULT_*`) and PowerShell (default-initialized) ports handled
it correctly.

The config parser prints three lines (context_files JSON, marker_start,
marker_end), captured via `_raw_opts="$(...)"`. Command substitution strips
trailing newlines, so blank marker lines collapse the output to fewer than
three, tripping the `(( ${#_opts_lines[@]} < 3 ))` guard and making the
DEFAULT_START/END substitution unreachable — the exact case it was written
for.

Require only the context_files line and default the marker lines to empty
(`${_opts_lines[1]:-}` / `${_opts_lines[2]:-}`) so the existing
DEFAULT_START/END fallback fills them in. Add a parity regression test with
blank markers (it fails on the old guard and passes with the fix).

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Mateus Cardoso
2026-07-27 12:33:56 -03:00
committed by GitHub
parent 99dc915ae3
commit 9e150cd3b2
2 changed files with 35 additions and 4 deletions

View File

@@ -176,13 +176,18 @@ _opts_lines=()
while IFS= read -r _line || [[ -n "$_line" ]]; do
_opts_lines+=("$_line")
done < <(printf '%s\n' "$_raw_opts")
if (( ${#_opts_lines[@]} < 3 )); then
echo "agent-context: malformed config parser output; expected 3 lines (context_files, marker_start, marker_end), got ${#_opts_lines[@]}; skipping update." >&2
if (( ${#_opts_lines[@]} < 1 )); then
echo "agent-context: malformed config parser output; expected at least the context_files line, got ${#_opts_lines[@]}; skipping update." >&2
exit 0
fi
# The marker lines may be absent: the $(...) capture above strips trailing
# newlines, so blank markers (the config omitting context_markers and relying on
# defaults) collapse the 3-line output to fewer lines. Default them to empty here
# and let the DEFAULT_START/END substitution below fill them in, matching the
# Python and PowerShell ports.
CONTEXT_FILES_JSON="${_opts_lines[0]}"
MARKER_START="${_opts_lines[1]}"
MARKER_END="${_opts_lines[2]}"
MARKER_START="${_opts_lines[1]:-}"
MARKER_END="${_opts_lines[2]:-}"
if ! _context_files_raw="$("$_python" - "$CONTEXT_FILES_JSON" <<'PY'
import json

View File

@@ -222,6 +222,32 @@ def test_python_custom_markers_matching_bash(tmp_path: Path) -> None:
assert "old" not in content
@requires_posix_bash
def test_python_blank_markers_use_defaults_matching_bash(tmp_path: Path) -> None:
# Regression: with blank markers (config relying on the built-in defaults),
# the Bash port must fall back to DEFAULT_START/END, matching the Python and
# PowerShell ports. Previously the Bash config-parser transport dropped the
# trailing empty marker lines under $(...) command substitution, tripping the
# "malformed config parser output" guard so the default-marker substitution
# became unreachable and the context file was never updated.
markers = {"start": "", "end": ""}
repo_a, repo_b = twin_projects(
tmp_path, context_file="AGENTS.md", context_markers=markers
)
add_plan(repo_a)
add_plan(repo_b)
bash = run_bash(repo_a)
py = run_python(repo_b)
assert_parity(bash, py, repo_a, repo_b)
content = (repo_b / "AGENTS.md").read_bytes()
assert content == (repo_a / "AGENTS.md").read_bytes()
assert b"<!-- SPECKIT START -->" in content
assert b"<!-- SPECKIT END -->" in content
assert b"at specs/001-demo/plan.md" in content
@requires_posix_bash
def test_python_multiple_context_files_dedup_matching_bash(tmp_path: Path) -> None:
files = ["AGENTS.md", "docs/CONTEXT.md", "AGENTS.md"]