CtrlK
BlogDocsLog inGet started
Tessl Logo

jbaruch/coding-policy

General-purpose coding policy for Baruch's AI agents

74

Quality

93%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide
SecuritybySnyk

Medium

Suggest reviewing before use

Overview
Quality
Evals
Security
Files

test_session_start.shhooks/tests/

#!/usr/bin/env bash
# Outcome-based tests for hooks/session-start.sh.
#
# Each case copies the entry script into a scratch hooks directory beside fake
# hooks written here, and picks them through SESSION_START_HOOKS.
#
# The harness drops `set -e` to aggregate results, so every fixture-setup
# command is checked explicitly and aborts with a fatal diagnostic on failure
# (rules/error-handling.md aggregate-reporting carve-out).
#
# Covers:
#   1. Several hooks report  -> one payload carrying every status, in order,
#      even when the last hook is silent (the tessl last-output-wins case).
#   2. No hook reports       -> no output, exit 0.
#   3. A hook exits non-zero -> its own status line; the others still arrive.
#   4. A hook prints non-JSON -> its own status line; the others still arrive.
#   5. Every manifest route delivers SessionStart statuses from a plugin path
#      with spaces, including Grok's args-ignoring Claude-settings import.
#   8. Portable run under a native agent (TESSL_AGENT=claude-code/codex) -> silent.
#   9. Portable run under another agent -> the consensus {"additionalContext"} form.
#  10. python3 and jq both fail to parse -> a status naming the parsers, not the hook.
#   6. jq only, no python3  -> the same merged payload.
#   7. Neither python3 nor jq -> the hooks still run; a warning, no payload.
#  11. A hooks directory whose name ends in a newline -> its hooks still run.
#
# Run: bash hooks/tests/test_session_start.sh
set -uo pipefail

die() { echo "fatal: $*" >&2; exit 2; }

cleanup() { [[ -n "${TMP:-}" ]] && ! rm -rf "$TMP" && echo "warn: could not remove $TMP" >&2; return 0; }

pass() { PASS=$((PASS+1)); }
fail() { FAIL=$((FAIL+1)); echo "  ✗ FAIL: $1" >&2; }

fake_hook() { # <name> <body>
  printf '#!/usr/bin/env bash\nset -euo pipefail\n%s\n' "$2" > "$DIR/$1.sh" || die "cannot write fake hook $1"
}

# run <hook names...> -> OUT, RC. RUN_PATH, when set, replaces PATH for the script.
run() {
  OUT="$(env ${RUN_PATH:+PATH="$RUN_PATH"} SESSION_START_HOOKS="$*" "$BASH" "$DIR/session-start.sh" </dev/null 2>"$TMP/err")"
  RC=$?
}

# A PATH holding only the named tools, linked from the real PATH.
only_tools() { # <dir> <tool...>
  local dir="$1" tool real; shift
  mkdir -p "$dir" || die "cannot create $dir"
  for tool in "$@"; do
    real="$(command -v "$tool")" || die "$tool is required for these tests"
    ln -s "$real" "$dir/$tool" || die "cannot link $tool into $dir"
  done
}

context() { python3 -c 'import json,sys; d=json.loads(sys.stdin.read())["hookSpecificOutput"]; assert d["hookEventName"] == "SessionStart"; print(d["additionalContext"])' <<<"$OUT"; }

main() {
  local here
  here="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" || die "cannot resolve the hooks directory"
  command -v python3 >/dev/null || die "python3 required for these tests"
  TMP="$(mktemp -d -t session-start-test.XXXXXX)" || die "mktemp failed"
  trap cleanup EXIT
  DIR="$TMP/hooks"
  mkdir -p "$DIR" || die "cannot create $DIR"
  cp "$here/session-start.sh" "$DIR/" || die "cannot copy session-start.sh"
  FAIL=0; PASS=0

  fake_hook one "printf '%s\n' '{\"additionalContext\":\"Session-start status — one\"}'"
  fake_hook two "printf '%s\n' '{\"additionalContext\":\"Session-start status — two\"}'"
  fake_hook quiet "exit 0"
  fake_hook broken "exit 3"
  fake_hook noisy "printf 'not json\n'"

  # 1. every status arrives, in order, despite a silent last hook.
  run one two quiet
  if [[ $RC -eq 0 ]] && [[ "$(context)" == $'Session-start status — one\n\nSession-start status — two' ]]; then
    pass; else fail "merge: expected both statuses in order, got RC=$RC OUT=$OUT"; fi

  # 2. nothing to report -> nothing printed.
  run quiet quiet
  if [[ $RC -eq 0 && -z "$OUT" ]]; then pass; else fail "silent: expected no output, got RC=$RC OUT=$OUT"; fi

  # 3. a failing hook is named, and the others still arrive.
  run one broken two
  if [[ $RC -eq 0 ]] && context | grep -qF "hook broken exited 3; run \`bash $DIR/broken.sh\`" && context | grep -q "— one" && context | grep -q "— two"; then
    pass; else fail "failed hook: expected a status naming it, got RC=$RC OUT=$OUT"; fi

  # 4. a hook printing non-JSON is named, and the others still arrive.
  run noisy one
  if [[ $RC -eq 0 ]] && context | grep -q "hook noisy printed something other than" && context | grep -q "— one"; then
    pass; else fail "bad output: expected a status naming it, got RC=$RC OUT=$OUT"; fi

  # 5. execute the manifest routes; quoting is proved by the delivered payload.
  if python3 - "$here/../.tessl-plugin/plugin.json" "$DIR" "$TMP" <<'PY'
import json, os, shlex, shutil, subprocess, sys
from pathlib import Path

d = json.loads(Path(sys.argv[1]).read_text())
plugin = Path(sys.argv[3]) / "installed plugin with spaces"
shutil.copytree(sys.argv[2], plugin / "hooks")
settings = plugin / ".claude"
settings.mkdir()
environment = {**os.environ, "SESSION_START_HOOKS": "one two quiet"}
environment.pop("TESSL_AGENT", None)
expected = "Session-start status — one\n\nSession-start status — two"
for agent in ("portable", "claude-code", "codex", "grok"):
    groups = d["hooks"]["SessionStart"] if agent == "portable" else d["nativeHooks"][
        "claude-code" if agent == "grok" else agent]["SessionStart"]
    entries = [entry for group in groups for entry in group["hooks"]]
    outputs = []
    for entry in entries:
        expand = lambda value: value.replace("${TESSL_PLUGIN_DIR}", str(plugin))
        command = expand(entry["command"])
        if agent == "grok":
            # Grok executes a compound string in sh, ignores args, and
            # resolves a bare command against the settings directory.
            argv = ["sh", "-c", command] if " " in command else [str(settings / command)]
        else:
            argv = shlex.split(command) + [expand(arg) for arg in entry.get("args", [])]
        current = {**environment, **({"TESSL_AGENT": "cursor"} if agent == "portable" else {})}
        result = subprocess.run(argv, env=current, input="", capture_output=True, text=True, check=False)
        assert result.returncode == 0, (agent, result.stderr)
        if result.stdout.strip():
            payload = json.loads(result.stdout)
            if agent != "portable":
                payload = payload["hookSpecificOutput"]
                assert payload["hookEventName"] == "SessionStart", agent
            outputs.append(payload["additionalContext"])
    assert outputs == [expected], (agent, outputs)
PY
  then pass; else fail "manifest: every SessionStart route must deliver both statuses once from installed paths with spaces"; fi

  # 6. jq alone merges the same way.
  command -v jq >/dev/null || die "jq required for these tests"
  only_tools "$TMP/jq-only" bash dirname jq
  RUN_PATH="$TMP/jq-only" run one two quiet noisy
  if [[ $RC -eq 0 ]] && [[ "$(context)" == $'Session-start status — one\n\nSession-start status — two\n\n'"Session-start status — hook noisy printed something other than one additionalContext object; run \`bash $DIR/noisy.sh\` from this repo to see it." ]]; then
    pass; else fail "jq only: expected the merged payload, got RC=$RC OUT=$OUT"; fi

  # 7. Neither tool: the hooks still act, and the loss is warned, not silent.
  fake_hook marker "printf 'ran\n' > \"$TMP/marker\""
  only_tools "$TMP/bare" bash dirname
  RUN_PATH="$TMP/bare" run marker one
  if [[ $RC -eq 0 && -z "$OUT" && -f "$TMP/marker" ]] && grep -q "neither python3 nor jq" "$TMP/err"; then
    pass; else fail "no JSON tool: expected hooks run, a warning and no payload, got RC=$RC OUT=$OUT err=$(cat "$TMP/err")"; fi

  # 8. the portable run defers to the native entry for claude-code and codex.
  local agent
  for agent in claude-code codex; do
    OUT="$(TESSL_AGENT="$agent" SESSION_START_HOOKS="one" bash "$DIR/session-start.sh" </dev/null 2>"$TMP/err")"; RC=$?
    if [[ $RC -eq 0 && -z "$OUT" ]]; then pass; else fail "portable under $agent: expected silence, got RC=$RC OUT=$OUT"; fi
  done

  # 9. another agent gets the consensus form tessl translates.
  OUT="$(TESSL_AGENT=cursor SESSION_START_HOOKS="one two" bash "$DIR/session-start.sh" </dev/null 2>"$TMP/err")"; RC=$?
  if [[ $RC -eq 0 ]] && python3 -c 'import json,sys; d=json.loads(sys.argv[1]); assert "hookSpecificOutput" not in d; assert d["additionalContext"] == "Session-start status — one\n\nSession-start status — two"' "$OUT"; then
    pass; else fail "portable under cursor: expected the consensus payload, got RC=$RC OUT=$OUT"; fi

  # 10. no parser can read a hook's output -> a status blaming the parsers, not the hook.
  local shims="$TMP/broken-parsers" realpy
  realpy="$(command -v python3)" || die "python3 required"
  mkdir -p "$shims" || die "cannot create $shims"
  # shellcheck disable=SC2016  # The format string is the shim's source: its ${2:-} and "$@" expand in the shim.
  printf '#!/usr/bin/env bash\ncase "${2:-}" in *json.loads*) exit 2 ;; esac\nexec %q "$@"\n' "$realpy" > "$shims/python3" \
    || die "cannot write the python3 shim"
  printf '#!/usr/bin/env bash\nexit 2\n' > "$shims/jq" || die "cannot write the jq shim"
  chmod +x "$shims/python3" "$shims/jq" || die "cannot make the shims executable"
  OUT="$(PATH="$shims:$PATH" SESSION_START_HOOKS="one" bash "$DIR/session-start.sh" </dev/null 2>"$TMP/err")"; RC=$?
  if [[ $RC -eq 0 ]] && context | grep -q "could not parse hook one's output" && ! context | grep -q "printed something other"; then
    pass; else fail "parser failure: expected a parser status, got RC=$RC OUT=$OUT err=$(cat "$TMP/err")"; fi

  # 11. a hooks directory whose name ends in a newline still finds its hooks;
  #     a `$(dirname ...)` capture would drop the newline (#487). The name
  #     must not collide with $DIR once stripped, or the old code would pass
  #     by running the plain directory's hooks.
  local plain_dir="$DIR"
  DIR="$TMP/"$'nl-hooks\n'
  mkdir -p "$DIR" || die "cannot create the newline-named hooks directory"
  cp "$plain_dir/session-start.sh" "$DIR/" || die "cannot copy session-start.sh"
  fake_hook one "printf '%s\n' '{\"additionalContext\":\"Session-start status — one\"}'"
  run one
  if [[ $RC -eq 0 ]] && [[ "$(context)" == 'Session-start status — one' ]]; then
    pass; else fail "newline-named hooks dir: expected hook one's status, got RC=$RC OUT=$OUT err=$(cat "$TMP/err")"; fi
  DIR="$plain_dir"

  echo "─────────────────────────────────────────────"
  if (( FAIL > 0 )); then echo "FAILED: ${FAIL} failed, ${PASS} passed"; exit 1; fi
  echo "PASSED: all ${PASS} checks"
}

if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
  main "$@"
fi

README.md

tile.json