| name | user-skip-telemetry |
| description | Counts Q&A skip events from the abort-safe-handler and qa-loop-ui outputs. Shows an optional skip-reason dropdown when a user-initiated skip occurred. Produces a structured skip_metrics_record suitable for PostHog ingestion. Used in Stage 4 of the /gaai:bootstrap pipeline after abort-safe-handler returns. |
| license | ELv2 |
| compatibility | Works in any interactive AI coding agent context (Claude Code, Cursor) where inline Q&A is supported |
| metadata | {"author":"gaai-framework","version":"1.0","category":"cross","track":"cross-cutting","id":"SKILL-USER-SKIP-TELEMETRY-001","updated_at":"2026-04-29T00:00:00.000Z","status":"stable"} |
| inputs | ["abort_safe_result","qa_session_result","workspace_id","workspace_size_class"] |
| outputs | ["skip_metrics_record"] |
User-Skip Telemetry
Purpose / When to Activate
Activate:
- Immediately after
abort-safe-handler returns — the orchestrator invokes this skill to capture skip event counts before proceeding to synthesis
- Works for all return paths: pre-loop skip, mid-loop skip-all, per-question skips, and no-skip sessions
- One invocation per bootstrap session
When abort_safe_result.abort_reason == "no_questions" (no questions were generated), return a
{skip_occurred: false, skip_type: "no_questions"} record with zero counts. No reason dropdown is
shown (no user-facing skip choice was presented).
What this skill does NOT do:
- Persist or accumulate counts across sessions (orchestrator responsibility — forward to PostHog)
- Display Q&A questions (that is
qa-loop-ui)
- Wrap or gate the Q&A loop (that is
abort-safe-handler)
- Enrich or analyse skip patterns across workspaces (V1.5+ analytics)
Input Schema
abort_safe_result:
skipped: bool
qa_answers: array | null
skip_telemetry:
skip_reason: string | null
skipped_at: string | null
workspace_id: string
response_time_ms: number | null
abort_reason: string | null
qa_session_result:
answers:
- question_id: string
answer_text: string
skipped: bool
response_time_ms: number
partial: bool
skipped_all: bool
abort_reason: string | null
qa_duration_ms: number
workspace_id: string
workspace_size_class: string | null
Process
Step 0 — No-questions shortcut
if abort_safe_result.abort_reason == "no_questions":
log: "[user-skip-telemetry] no questions were generated — Stage 4 was a no-op"
return {
skip_occurred: false,
skip_type: "no_questions",
skip_reason: null,
per_question_skips: [],
questions_presented: 0,
questions_skipped: 0,
questions_answered: 0,
workspace_id: workspace_id,
workspace_size_class: workspace_size_class ?? null,
recorded_at: iso_timestamp()
}
Step 1 — Classify skip type
Determine skip_type using the following priority order:
if abort_safe_result.skip_telemetry?.skip_reason == "user_abort":
skip_type = "user_abort" # involuntary — Ctrl+C / context terminated
elif abort_safe_result.skip_telemetry?.skip_reason == "user_skip_entire":
skip_type = "pre_loop" # user chose [N] at the Stage 4 entry gate
elif qa_session_result != null AND qa_session_result.skipped_all == true:
skip_type = "mid_loop_skip_all" # user typed "skip all" during the Q&A loop
elif qa_session_result != null AND
qa_session_result.answers.some(a => a.skipped == true):
skip_type = "per_question_only" # some questions skipped individually, no skip-all
else:
skip_type = "none" # user answered all questions
Log classification:
log: "[user-skip-telemetry] skip_type={skip_type} workspace_id={workspace_id}"
Step 2 — Per-question skip metrics (AC1)
Compute per-question metrics from qa_session_result.answers if available. Record position only —
no question content or question_id text must appear in the metrics record (privacy-respecting aggregate).
per_question_skips = []
questions_presented = 0
questions_skipped = 0
questions_answered = 0
if qa_session_result != null AND qa_session_result.answers != null:
for i, answer in enumerate(qa_session_result.answers):
questions_presented++
if answer.skipped == true:
questions_skipped++
per_question_skips.push({position: i + 1, skipped: true})
else:
questions_answered++
per_question_skips.push({position: i + 1, skipped: false})
per_question_skips contains only position integers and boolean flags — no text, no topic names,
no question IDs that could be reverse-mapped to question content.
Step 3 — Optional reason capture (AC2)
Show the reason dropdown ONLY when skip_type is "pre_loop" or "mid_loop_skip_all" (user made
a deliberate skip choice). Do NOT show for "user_abort" (involuntary) or "per_question_only"
(individual question skips, covered by per_question_skips already) or "none".
skip_reason = null
if skip_type == "pre_loop" OR skip_type == "mid_loop_skip_all":
display:
"(Optional) Why did you skip the Q&A? Press Enter to skip this.
1. not relevant
2. too vague
3. time-pressed
4. other"
raw_input = trim(await_user_input())
normalized = lowercase(raw_input)
skip_reason = match normalized:
"1" | "not relevant" → "not_relevant"
"2" | "too vague" → "too_vague"
"3" | "time-pressed" → "time_pressed"
"4" | "other" → "other"
"" → null # user skipped the reason prompt
_ → null # unrecognised input treated as no reason
If skip_type is NOT "pre_loop" or "mid_loop_skip_all", set skip_reason = null and do NOT
prompt the user.
Step 4 — Produce skip_metrics_record (AC3)
recorded_at = iso_timestamp()
skip_metrics_record = {
event: "bootstrap.qa_skip",
skip_occurred: (skip_type != "none" AND skip_type != "no_questions"),
skip_type: skip_type,
skip_reason: skip_reason,
per_question_skips: per_question_skips,
questions_presented: questions_presented,
questions_skipped: questions_skipped,
questions_answered: questions_answered,
workspace_id: workspace_id,
workspace_size_class: workspace_size_class ?? null,
recorded_at: recorded_at
}
log: "[user-skip-telemetry] metrics record produced:
skip_type={skip_type} skip_reason={skip_reason ?? "none"}
questions={questions_presented} skipped={questions_skipped} answered={questions_answered}"
return skip_metrics_record
Orchestrator responsibility after this skill (AC3)
The orchestrator MUST forward skip_metrics_record to PostHog as a fire-and-forget event:
gaai_admin(action: "log_posthog",
event: skip_metrics_record.event,
props: skip_metrics_record)
This call is non-blocking. If PostHog is unreachable or POSTHOG_API_KEY is absent, the event is
silently dropped — never block bootstrap continuation on telemetry delivery.
The orchestrator then proceeds to bootstrap-llm-synthesis regardless of telemetry outcome.
Output Schema
skip_metrics_record:
event: "bootstrap.qa_skip"
skip_occurred: bool
skip_type:
"pre_loop"
| "mid_loop_skip_all"
| "per_question_only"
| "none"
| "user_abort"
| "no_questions"
skip_reason: "not_relevant" | "too_vague" | "time_pressed" | "other" | null
per_question_skips:
- position: number
skipped: bool
questions_presented: number
questions_skipped: number
questions_answered: number
workspace_id: string
workspace_size_class: "solo" | "small" | "medium" | null
recorded_at: string
skip_metrics_record:
event: "bootstrap.qa_skip"
skip_occurred: true
skip_type: "pre_loop"
skip_reason: "time_pressed"
per_question_skips: []
questions_presented: 0
questions_skipped: 0
questions_answered: 0
workspace_id: "ws_abc123"
workspace_size_class: "solo"
recorded_at: "2026-04-29T10:32:15Z"
skip_metrics_record:
event: "bootstrap.qa_skip"
skip_occurred: true
skip_type: "per_question_only"
skip_reason: null
per_question_skips:
- {position: 1, skipped: false}
- {position: 2, skipped: true}
- {position: 3, skipped: false}
- {position: 4, skipped: false}
questions_presented: 4
questions_skipped: 1
questions_answered: 3
workspace_id: "ws_abc123"
workspace_size_class: null
recorded_at: "2026-04-29T10:40:22Z"
skip_metrics_record:
event: "bootstrap.qa_skip"
skip_occurred: false
skip_type: "none"
skip_reason: null
per_question_skips:
- {position: 1, skipped: false}
- {position: 2, skipped: false}
- {position: 3, skipped: false}
questions_presented: 3
questions_skipped: 0
questions_answered: 3
workspace_id: "ws_abc123"
workspace_size_class: "small"
recorded_at: "2026-04-29T10:45:00Z"
Quality Checks
skip_occurred: true implies skip_type is NOT "none" and NOT "no_questions"
skip_type: "pre_loop" implies per_question_skips == [] and questions_presented == 0
skip_type: "no_questions" implies all counts are 0 and per_question_skips == []
skip_reason is non-null ONLY for skip_type in ["pre_loop", "mid_loop_skip_all"]
questions_skipped + questions_answered == questions_presented
per_question_skips.length == questions_presented
per_question_skips[i].position is 1-based and sequential (1, 2, 3, …)
- No
question_id, question_text, topic, or answer_text fields in per_question_skips
recorded_at is always a non-null ISO 8601 UTC string
Non-Goals
This skill MUST NOT:
- Aggregate or persist skip counts across multiple bootstrap sessions (orchestrator / PostHog responsibility)
- Compare skip rates across workspaces or flag anomalies (V1.5+ analytics)
- Block bootstrap continuation when PostHog delivery fails (fire-and-forget, non-blocking)
- Capture answer content or question text in the metrics record (privacy invariant)
- Prompt for skip reason on
"user_abort" or "per_question_only" skip types
- Invoke
abort-safe-handler or qa-loop-ui (this skill is post-loop only)
- Make governance decisions based on skip patterns (observe, not decide — per the active bootstrap-telemetry DEC, if any)