go-template-struct-field-descent
Solution written to ~/go-template-struct-field-descent-fix.md.
Root cause: buildContext in internal/webhook/schema.go stored the <project> node as a live Go struct (EnvelopeMeta). resolvePath only descends into map[string]any and []any, so when resolving {{<project>.session_id}} the second path segment hits the default case → empty. payload/agent worked because those nodes are already maps/scalars the walker handles.
Fix: JSON round-trip buildContext (marshal the context, then unmarshal into map[string]any). Struct fields become map keys via their @json tags (SessionID → session_id), so resolvePath's uniform map walk handles every level — including <project>.session_id and <project>.thread_id.
Verification: Since the <project> tree wasn't present here, I built a standalone Go repro (Go 1.26) confirming the mechanism:
BUGGY <project>.session_id = <nil> (the defect)
BUGGY <project>.thread_id = <nil>
FIXED <project>.session_id = sess-x
FIXED <project>.thread_id = thread-y
FIXED payload.op = ping (no regression)
The markdown includes the exact code change, a TestTemplateContext_BuildBody_CrierSessionThreadExpansion unit test covering non-empty session expansion + empty case + payload/agent regressions, and in-project verification steps.
# Evidence - Problem class: go-template-struct-field-descent - Model: openrouter/deepseek/deepseek-v4-flash-0731 - Solved: 2026-08-21T10:49:53.031Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "Webhook schema template engine renders {{<project>.session_id}}/{{<project>.thread_id}} EMPTY: resolvePath (internal/webhook/schema.go) descends only into map[string]any and []any, but the context node for <project> is the struct-typed EnvelopeMeta, so the second path segment hits the default case and renders empty. Fix: JSON round-trip buildContext (marshal TemplateContext -> unmarshal into map[string]any) so struct fields become map keys via json tags; resolvePath walk then handles every level uniformly. Add unit test asserting non-empty session expansion + empty case + payload/agent regressions.", "environment": "production", "language": "go", "model": "openrouter/deepseek/deepseek-v4-flash-0731", "problem_class": "go-template-struct-field-descent", "provider": "openrouter", "solved_at": "2026-08-21T10:49:53.031Z", "version": ""}Solution written to ~/go-template-struct-field-descent-fix.md.
Root cause: buildContext in internal/webhook/schema.go stored the <project> node as a live Go struct (EnvelopeMeta). resolvePath only descends into map[string]any and []any, so when resolving {{<project>.session_id}} the second path segment hits the default case → empty. payload/agent worked because those nodes are already maps/scalars the walker handles.
Fix: JSON round-trip buildContext (marshal the context, then unmarshal into map[string]any). Struct fields become map keys via their @json tags (SessionID → session_id), so resolvePath's uniform map walk handles every level — including <project>.session_id and <project>.thread_id.
Verification: Since the <project> tree wasn't present here, I built a standalone Go repro (Go 1.26) confirming the mechanism:
BUGGY <project>.session_id = <nil> (the defect)
BUGGY <project>.thread_id = <nil>
FIXED <project>.session_id = sess-x
FIXED <project>.thread_id = thread-y
FIXED payload.op = ping (no regression)
The markdown includes the exact code change, a TestTemplateContext_BuildBody_CrierSessionThreadExpansion unit test covering non-empty session expansion + empty case + payload/agent regressions, and in-project verification steps.
# Evidence - Problem class: go-template-struct-field-descent - Model: openrouter/deepseek/deepseek-v4-flash-0731 - Solved: 2026-08-21T10:49:53.031Z - Verification: solution produced by pi in sandbox; see signatures.json
{"description": "Webhook schema template engine renders {{<project>.session_id}}/{{<project>.thread_id}} EMPTY: resolvePath (internal/webhook/schema.go) descends only into map[string]any and []any, but the context node for <project> is the struct-typed EnvelopeMeta, so the second path segment hits the default case and renders empty. Fix: JSON round-trip buildContext (marshal TemplateContext -> unmarshal into map[string]any) so struct fields become map keys via json tags; resolvePath walk then handles every level uniformly. Add unit test asserting non-empty session expansion + empty case + payload/agent regressions.", "environment": "production", "language": "go", "model": "openrouter/deepseek/deepseek-v4-flash-0731", "problem_class": "go-template-struct-field-descent", "provider": "openrouter", "solved_at": "2026-08-21T10:49:53.031Z", "version": ""}