TestHookSessionStartRequiresSkillInvocation pins the invariant that the injected additionalContext explicitly instructs the session to invoke the flow skill via the Skill tool as its first action, and mentions the task slug so the agent has something anchor-visible. The hook discovers the bound task
(t *testing.T)
| 72 | // $CLAUDE_CODE_SESSION_ID (set by Claude Code in every real session) |
| 73 | // rather than by reading FLOW_TASK. |
| 74 | func TestHookSessionStartRequiresSkillInvocation(t *testing.T) { |
| 75 | setupFlowRoot(t) |
| 76 | |
| 77 | // Seed a task and pin its session_id so the reverse-lookup finds it. |
| 78 | seedTask(t, "some-slug") |
| 79 | const sid = "deadbeef-1234-4567-8abc-def012345678" |
| 80 | db := openFlowDB(t) |
| 81 | if _, err := db.Exec( |
| 82 | `UPDATE tasks SET session_id=?, status='in-progress', session_started=? WHERE slug='some-slug'`, |
| 83 | sid, flowdb.NowISO(), |
| 84 | ); err != nil { |
| 85 | t.Fatal(err) |
| 86 | } |
| 87 | t.Setenv("CLAUDE_CODE_SESSION_ID", sid) |
| 88 | |
| 89 | out := captureStdout(t, func() { |
| 90 | if rc := cmdHookSessionStart(nil); rc != 0 { |
| 91 | t.Fatalf("rc=%d", rc) |
| 92 | } |
| 93 | }) |
| 94 | |
| 95 | var parsed struct { |
| 96 | HookSpecificOutput struct { |
| 97 | HookEventName string `json:"hookEventName"` |
| 98 | AdditionalContext string `json:"additionalContext"` |
| 99 | } `json:"hookSpecificOutput"` |
| 100 | } |
| 101 | if err := json.Unmarshal([]byte(out), &parsed); err != nil { |
| 102 | t.Fatalf("parse hook output: %v\nraw: %s", err, out) |
| 103 | } |
| 104 | if parsed.HookSpecificOutput.HookEventName != "SessionStart" { |
| 105 | t.Errorf("hookEventName = %q, want SessionStart", parsed.HookSpecificOutput.HookEventName) |
| 106 | } |
| 107 | ctx := parsed.HookSpecificOutput.AdditionalContext |
| 108 | if !strings.Contains(ctx, "Skill tool") { |
| 109 | t.Errorf("additionalContext must instruct Skill tool invocation, got:\n%s", ctx) |
| 110 | } |
| 111 | if !strings.Contains(ctx, "`flow` skill") { |
| 112 | t.Errorf("additionalContext must name the `flow` skill, got:\n%s", ctx) |
| 113 | } |
| 114 | // Self-registration is gone — the UUID is pre-allocated by `flow do`. |
| 115 | // Make sure we don't regress by re-introducing it here. |
| 116 | if strings.Contains(ctx, "register-session") { |
| 117 | t.Errorf("additionalContext should not mention register-session (pre-allocated by flow do):\n%s", ctx) |
| 118 | } |
| 119 | if !strings.Contains(ctx, "some-slug") { |
| 120 | t.Errorf("additionalContext should mention the task slug, got:\n%s", ctx) |
| 121 | } |
| 122 | } |
| 123 | |
| 124 | // TestHookUserPromptSubmitBoundEmitsAnchor pins the bound-session |
| 125 | // contract: when the current $CLAUDE_CODE_SESSION_ID belongs to a task, |
nothing calls this directly
no test coverage detected