CtrlK
BlogDocsLog inGet started
Tessl Logo

jbaruch/coding-policy

General-purpose coding policy for Baruch's AI agents

73

Quality

91%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Low

Low-risk findings worth noting

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. The manifest declares session-start.sh as the only SessionStart hook:
#      portable, plus native for claude-code and codex.
#   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. the manifest routes SessionStart through this script alone.
  if python3 - "$here/../.tessl-plugin/plugin.json" <<'PY'
import json, sys
d = json.load(open(sys.argv[1]))
portable = [h for g in d["hooks"]["SessionStart"] for h in g["hooks"]]
claude = [h for g in d["nativeHooks"]["claude-code"]["SessionStart"] for h in g["hooks"]]
codex = [h for g in d["nativeHooks"]["codex"]["SessionStart"] for h in g["hooks"]]
ok = (len(portable) == 1 and portable[0]["args"] == ["${TESSL_PLUGIN_DIR}/hooks/session-start.sh"]
      and len(claude) == 1 and claude[0]["args"] == ["${TESSL_PLUGIN_DIR}/hooks/session-start.sh"]
      and len(codex) == 1 and codex[0]["command"] == 'bash "${TESSL_PLUGIN_DIR}/hooks/session-start.sh"')
sys.exit(0 if ok else 1)
PY
  then pass; else fail "manifest: SessionStart must be session-start.sh alone: portable, plus native for claude-code and codex"; 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