Skip to content

skillscript-runtime v0.19.12

Choose a tag to compare

@github-actions github-actions released this 14 Jun 23:45

Perry built pr-review-prep (a body-template skill calling
gh pr list) and hit two real bugs in succession.

Finding 1 (HIGH) — runtime_capabilities.shellExecution was lying

The surface claimed "any binary on PATH may be invoked" — written
pre-v0.18.8 and never updated when the default-deny shell allowlist
shipped. Discovery surface contradicted enforcement. Perry twice gave
Scott wrong operator guidance based on reading the false claim.

Fix: shellExecution now reports the actual allowlist (array
when wired, "(unset — default-deny; no shell ops will run)"
otherwise) + a description that explains the allowlist-gated reality
and the three wiring paths (SKILLSCRIPT_SHELL_ALLOWLIST env,
shellAllowlist field in skillscript.config.json, programmatic
bootstrap({shellAllowlist: [...]})).

Finding 2a — |fallback: template filter wasn't empty-aware

${PRS|fallback:"No current PRs."} did NOT fire when PRS bound to
empty string (the canonical gh pr list no-results case writes
nothing to stdout). Pre-fix the filter fired only on undefined.

Fix: aligned with the $-op trailer semantic — fires on
empty-string-after-trim OR empty-array OR null/undefined. One
emptiness predicate across both fallback surfaces.

Finding 2b — (fallback:) op-trailer was silently no-oped on shell()

file_read honors (fallback:) on throw (catch error → bind
fallback + record). shell() didn't — the trailer parsed but the
runtime ignored it. Perry's "the runtime-intrinsic op-trailer isn't
expected to fire" framing was actually a missing implementation; the
file_read precedent shows it should work.

Fix: shell() now honors (fallback: "value") on throw OR empty
stdout, uniformly across all three forms (command=, argv=,
unsafe=true). Wrapped via a shared recordShellFallback helper
that pushes a fallbacks[] record (matches file_read's pattern).

Bonus — closes the buried-root-error case

Pre-fix, when shell threw + a template referenced the unbound var,
the surface error read as UnresolvedVariableError on the template;
the root ShellBinaryNotAllowedError was buried in errors[0]. With
F2b shipped, the canonical case dissolves: shell op catches its error
via the fallback path, binds the fallback value, template renders
cleanly. No buried error.

What changed

  • src/mcp-server.ts:1116shellExecution reports allowlist field
    • accurate description; drops the false "any binary on PATH" claim.
  • src/runtime.ts:1822|fallback: filter empty-aware semantic.
  • src/runtime.ts shell case — try/catch + empty-stdout coverage on
    argv + command= paths; recordShellFallback helper closure pushes
    fallback records to fallbacks[].
  • src/help-content.tsshell() op description includes the
    unified fallback semantic + cross-link to the |fallback: filter.
  • tests/v0.19.12-shell-runtime-capabilities-fallback.test.ts — 11
    regression tests across all three findings (runtime_capabilities
    accuracy with/without allowlist; filter empty-aware on
    undefined/empty/whitespace/non-empty; shell op fallback on throw +
    empty stdout in argv + command= forms; Perry's canonical
    gh-empty-stdout scenario).
  • scripts/loc-ceiling.mjs — narrow ceiling 11400 → 11500.

1924 total tests (+11 net new).

Migration

No migration required. The filter change is empty-aware-additive
(values that previously rendered as empty now render as the fallback
arg). The shell op-trailer change is additive (skills with no
fallback continue to throw exactly as before; skills with fallback
now have it honored). The runtime_capabilities change is read-only
surface accuracy.