Skip to content

[Sprint] sprint-loop-34 - #30

Merged
scealiontach merged 8 commits into
mainfrom
sprint/2026-05-05-sprint-loop-34
May 5, 2026
Merged

scealiontach merged 8 commits into
mainfrom
sprint/2026-05-05-sprint-loop-34

Conversation

@scealiontach

Copy link
Copy Markdown
Owner

Sprint Plan: 2026-05-05 — sprint-loop-34

Sprint Goal

Harden the bash/ library against a cluster of small, latent correctness
bugs and ergonomic anti-patterns that have been catalogued in the Backlog
but never picked up. Each selected issue is a self-contained, low-blast-
radius fix in either a single command script (changelog, aliases,
pretty-labels) or a single library function (commands::use,
log::_format, the log.sh source-time guard, secret::_install_cleanup_trap).
Every fix lands with a corresponding bats regression spec so the
behaviour does not silently regress again. The sprint is sized so all
seven items can ship as independent commits on one feature branch and
roll up into a single PR per issue.

Selected Issues

1. SUR-2330 — Bug: aliases update_kubectl missing mkdir -p $HOME/.local/bin before install

  • Description summary: update_kubectl calls install -m 0755 "$tmpdir/kubectl" "$HOME/.local/bin/kubectl" (line 226) without first
    ensuring ~/.local/bin exists. Peer functions update_eksctl (line 89)
    and update_helm (line 148) both mkdir -p first. On a fresh machine
    the install hard-fails with cannot create regular file ... No such file or directory.
  • Rationale: One-line mechanical fix to bring update_kubectl into
    parity with its two siblings. New-machine UX bug; the kind of thing
    that bites exactly when someone is trying the script for the first
    time. Self-contained; no library impact.
  • Definition of Done:
    • mkdir -p "$HOME/.local/bin" (or equivalent install -D) added
      immediately before the existing install call in update_kubectl.
    • All three update_* functions consistent in their dir-creation
      style.
    • tests/aliases.bats (new or extended) covers: with ~/.local/bin
      absent, update_kubectl succeeds and the binary is installed.
    • make test and pre-commit run --all-files pass.
  • Dependencies / ordering: None. Can ship first or last.

2. SUR-2342 — Bug: commands::use breaks for command names containing hyphens

  • Description summary: commands::use builds the cache-key variable
    as _${cmd} and then declare -g "${var}=$path". When $cmd contains
    a hyphen or dot (ssh-keygen, aws-vault, python3.12), the literal
    variable name is rejected by bash:
    declare: '_ssh-keygen=...': not a valid identifier. Distinct from
    the closed SUR-1810 fix.
  • Rationale: Foundational fix in the canonical "look up an external
    tool" helper that AGENTS.md actively encourages new code to use. Latent
    today (current callers are all hyphen-free), but every future caller
    needing ssh-keygen / pre-commit / python3.12 would hit it.
  • Definition of Done:
    • commands::use sanitises non-identifier characters ([^A-Za-z0-9_] → _) when constructing the cache-key variable name.
    • command -v "$cmd" continues to use the unsanitised original name
      (we resolve ssh-keygen, not ssh_keygen).
    • New tests/commands.bats cases: stub ssh-keygen on PATH, assert
      commands::use ssh-keygen returns the stubbed path; second
      invocation hits the cache without re-resolving.
    • Existing hyphen-free callers (awk, jq, kubectl, etc.) still
      pass their bats coverage.
    • make test and pre-commit run --all-files pass.
  • Dependencies / ordering: None. Touches only bash/commands.sh and
    tests/commands.bats.

3. SUR-2347 — Improvement: log.sh source-time init guard skips ALL LOG_DISABLE_* flags if any one is pre-set

  • Description summary: At bash/log.sh:89–96 the source-time guard
    initialises LOG_DISABLE_* from LOG_LEVEL only when none of the
    four disable flags are pre-set. If a caller pre-sets exactly one
    (e.g. LOG_DISABLE_INFO=false), the other three remain unset (empty),
    and [ "$LOG_DISABLE_TRACE" = "false" ] evaluates false, silently
    disabling those levels regardless of LOG_LEVEL.
  • Rationale: Surprising silent log-line drops are notoriously hard
    to debug. Distinct from closed SUR-1925. Fix is a small, contained
    refactor of one source-time block.
  • Definition of Done:
    • The guard initialises each LOG_DISABLE_* flag independently when
      unset; explicit caller pre-sets are preserved.
    • tests/log.bats adds: pre-set LOG_DISABLE_INFO=false only,
      LOG_LEVEL=4, source log.sh, assert all four flags are false.
    • No other behaviour change observable in existing log.bats specs.
    • make test and pre-commit run --all-files pass.
  • Dependencies / ordering: Touches bash/log.sh and
    tests/log.bats. Should land before or after SUR-2331 (separate
    commits; orthogonal line ranges).

4. SUR-2331 — Bug: log::_format sequential substitution lets messages inject %LEVEL/%PID/%DATE placeholders

  • Description summary: log::_format substitutes %MESSAGE first,
    then %LEVEL/%PID/%DATE, so any literal placeholder text inside
    the user-supplied message gets re-interpreted by the subsequent
    substitutions. log::info "request: %LEVEL" prints ... INFO request: INFO instead of ... INFO request: %LEVEL.
  • Rationale: Output-corruption footgun. Trivially fixed by
    substituting %MESSAGE last; alternatively switch to printf
    positional %ss.
  • Definition of Done:
    • log::_format substitutes %LEVEL, %PID, %DATE before
      %MESSAGE (or moves to printf with positional args).
    • tests/log.bats adds: log::_format INFO "literal %LEVEL" output
      contains the literal string %LEVEL, never the substituted level
      name.
    • Existing log.bats coverage unchanged.
    • make test and pre-commit run --all-files pass.
  • Dependencies / ordering: Same file as SUR-2347 (bash/log.sh).
    Land in a separate commit. Either order works; lines 89–96 vs 205–220
    do not conflict.

5. SUR-2327 — Bug: changelog with only -f or only -t fails

  • Description summary: bash/changelog documents -f FROM_DATE and
    -t TO_DATE as independent, but the wiring requires both: -f alone
    prints "No change history in the requested range"; -t alone hits
    ::fromto's ${1:?} parameter-null error.
  • Rationale: Documented options that silently fail or explode in
    exactly the natural "give me a changelog since date X" use case.
    Fix is to default each missing endpoint to a sensible ref (HEAD for
    to, repo-root commit for from).
  • Definition of Done:
    • -f alone produces a non-empty changelog ending at HEAD.
    • -t alone produces a non-empty changelog starting at the repo root.
    • Both flags together continue to produce the bounded range.
    • The "No change history in the requested range" exit branch is
      removed when at least one of -f/-t is set.
    • tests/changelog.bats (new or extended) covers all three cases.
    • make test and pre-commit run --all-files pass.
  • Dependencies / ordering: None. Touches only bash/changelog and
    tests/changelog.bats.

6. SUR-2332 — Bug: pretty-labels emits a stray header row from kubectl --show-labels

  • Description summary: bash/pretty-labels runs kubectl get $RESOURCE_TYPE --show-labels without --no-headers, so the header
    row survives the awk extraction as "NAME:LABELS" and produces one
    junk row per invocation. The closed SUR-1841 explicitly flagged this
    anti-pattern as generic; pretty-labels was missed.
  • Rationale: One-flag fix at the kubectl source. Cosmetic but
    consistent; affects every invocation. Mockable with a stubbed kubectl
    in bats.
  • Definition of Done:
    • pretty-labels invokes kubectl get ... --show-labels --no-headers "$@", eliminating the header row at the source.
    • tests/pretty-labels.bats (new) mocks kubectl to emit
      header + one row, asserts output contains exactly the per-row lines
      and no LABELS literal.
    • make test and pre-commit run --all-files pass.
  • Dependencies / ordering: None. Touches only bash/pretty-labels
    and tests/pretty-labels.bats.

7. SUR-2324 — Bug: secret::_install_cleanup_trap clobbers caller-defined EXIT/INT/TERM traps

  • Description summary: secret::_install_cleanup_trap (added by
    SUR-1839) installs trap 'secret::clear' EXIT INT TERM unconditionally
    on first secret registration, silently overwriting any caller-installed
    EXIT/INT/TERM trap. Resource leaks follow.
  • Rationale: Latent bug in a security-adjacent helper. Fix is to
    capture the existing trap with trap -p and chain it after
    secret::clear. Has clear bats reproducer.
  • Definition of Done:
    • secret::_install_cleanup_trap captures any pre-existing EXIT (and
      optionally INT/TERM) trap and chains it after secret::clear.
    • tests/secret.bats (new or extended) installs a caller trap that
      touches a sentinel file, calls secret::register_env, exits
      cleanly in a subshell, and asserts both the secret tempfiles are
      gone and the caller sentinel was created.
    • The existing SECRET_TRAP_INSTALLED idempotency guard remains.
    • make test and pre-commit run --all-files pass.
  • Dependencies / ordering: None. Touches only bash/secret.sh and
    tests/secret.bats.

Risks + Mitigations

  • Risk: bash/log.sh merge friction between SUR-2347 and SUR-2331.
    Both modify log.sh and tests/log.bats. Mitigation: land them in
    separate commits in known order; rebase the second on the first
    before pushing.
  • Risk: commands::use sanitisation could shadow distinct commands
    that differ only by hyphen vs underscore (e.g. foo-bar vs foo_bar
    both map to cache key _foo_bar).
    Mitigation: such collisions
    are extraordinarily unlikely on a real PATH; document in a function
    header comment that the sanitiser is for cache-key validity only and
    the command -v lookup uses the original name. Add an assertion in
    the bats test.
  • Risk: update_kubectl and update_helm/update_eksctl parity
    drifts further
    if the fix is applied only to update_kubectl.
    Mitigation: prefer install -D -m 0755 for all three (pulls dir
    creation inside install itself) — but only if the change can be
    scoped within SUR-2330's intended blast radius without becoming a
    cross-issue refactor.
  • Risk: pretty-labels --no-headers flag may conflict with
    user-supplied flags via "$@".
    Mitigation: insert --no-headers
    before "$@" so a user can still suppress it intentionally if needed
    (last-flag-wins in kubectl). Cover in bats.
  • Risk: secret::_install_cleanup_trap chained trap with embedded
    single quotes in caller's prior trap could break the
    reconstruction.
    Mitigation: prefer the documented trap -p EXIT
    capture pattern and quote-escape carefully; add a bats case where the
    caller trap contains a single quote.
  • Risk: changelog default endpoints (HEAD, repo-root commit) may
    surprise existing users.
    Mitigation: document the default in
    --help; add tests/changelog.bats cases that pin the new
    behaviour.
  • Risk: bats-core submodule not initialised in CI. Mitigation:
    CI already runs make test; verify the submodule init step is
    present, otherwise add git submodule update --init to the verify
    plan.
  • Risk: pre-commit no-commit-to-branch hook rejects the sprint
    branch on commit if branch name fails the ^(fix|feature|refactor|sprint)/[a-zA-Z0-9-]+$ regex.
    Mitigation: branch
    name sprint/2026-05-05-sprint-loop-34 matches the regex; verified.

Out of Scope

  • Any change to bash/release-images log-level forcing (SUR-2344) —
    not selected this sprint.
  • Any change to pagerduty::send_incident xtrace silencing (SUR-2339).
  • Any change to aws::wait_for_scan_complete timeout/max-attempts
    (SUR-2340).
  • Any change to options::add -m mandatory-flag enforcement (SUR-2322).
  • Any change to docker::registrycmd null-token handling (SUR-2337).
  • Any change to switch-to-branch -n fallback semantics (SUR-2334).
  • Cross-cutting refactors to a unified bin-install helper across
    update_kubectl / update_helm / update_eksctl (beyond the local
    parity fix).
  • Migrating log::_format from sequential parameter substitution to a
    full printf-based formatter (only the placeholder-ordering fix is
    in scope; deeper rewrite is out).
  • Any new library, command script, or @include graph change.

Linear Evidence

  • Linear team verified: Surinis (id ce9ebfde-ff2b-4f54-90f1-c388591ca110)
  • Linear project used: shell-scripts (id a43901a0-b02b-4009-aae1-a6e8903d127d)
  • Query/filter used: mcp__linear__list_issues with
    team=Surinis, project=shell-scripts, state=Backlog, limit=100;
    per-issue follow-ups via mcp__linear__get_issue with
    includeRelations=true, sub-issue scan via mcp__linear__list_issues
    with parentId=<id>, comments via mcp__linear__list_comments.
  • Approx count of Backlog issues reviewed: 13
  • Approx count of manual-labelled Backlog issues skipped: 0
  • Issues skipped due to unmerged blockers: 0 — []
  • Issues skipped due to open-PR file overlap: 0 — [] (the
    <OPEN_PR_FILES> list was empty, so Filter B was a no-op)

Sub-issue Status

None of the 13 Backlog issues had any sub-issues. Each
mcp__linear__list_issues parentId=<ID> query returned an empty list.

Parent Issue Sub-issue Sub-issue Status Eligible?
SUR-2330 (none) n/a yes
SUR-2342 (none) n/a yes
SUR-2347 (none) n/a yes
SUR-2331 (none) n/a yes
SUR-2327 (none) n/a yes
SUR-2332 (none) n/a yes
SUR-2324 (none) n/a yes

Linear State Transitions

Issue ID Previous State New State
SUR-2330 Backlog Todo
SUR-2342 Backlog Todo
SUR-2347 Backlog Todo
SUR-2331 Backlog Todo
SUR-2327 Backlog Todo
SUR-2332 Backlog Todo
SUR-2324 Backlog Todo

@scealiontach
scealiontach marked this pull request as ready for review May 5, 2026 15:00
Signed-off-by: Kevin O'Donnell <kodonnel@gmail.com>
…(SUR-2342)

Signed-off-by: Kevin O'Donnell <kodonnel@gmail.com>
Signed-off-by: Kevin O'Donnell <kodonnel@gmail.com>
…der injection (SUR-2331)

Signed-off-by: Kevin O'Donnell <kodonnel@gmail.com>
…UR-2327)

Signed-off-by: Kevin O'Donnell <kodonnel@gmail.com>
Signed-off-by: Kevin O'Donnell <kodonnel@gmail.com>
…SUR-2324)

Signed-off-by: Kevin O'Donnell <kodonnel@gmail.com>
The original SUR-2327 fix defaulted any unresolved endpoint to HEAD or
the repo root so -f and -t could be used independently. That conflated
two cases: (a) the user did not pass the flag, and (b) the user passed
the flag but the date matched no commit. Case (b) now silently expanded
the range to the opposite of what was requested — e.g. -t 1900-01-01
dumped full history instead of reporting "no commits before that date".

Distinguish the two before applying the default. If the user supplied
the flag but the corresponding date_to_commit lookup returned empty,
print "No commits before <date>" / "No commits on or after <date>" and
exit 0. Otherwise apply the per-flag default as before.

Updated the existing 2999-01-01 fixtures to 2050-01-01: git rev-list's
approxidate parser silently misparses 2999, which masked the regression
by making the broken-default path indistinguishable from the fixed one.

Adds two regression tests covering -t with a pre-history date and -f
with a post-history date; both must NOT print any commit message.

Signed-off-by: Kevin O'Donnell <kodonnel@gmail.com>
@scealiontach
scealiontach force-pushed the sprint/2026-05-05-sprint-loop-34 branch from 450a7e9 to 3ece592 Compare May 5, 2026 17:07
@scealiontach
scealiontach merged commit dc29b9a into main May 5, 2026
3 checks passed
@scealiontach
scealiontach deleted the sprint/2026-05-05-sprint-loop-34 branch May 5, 2026 17:22
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