This report documents the complete build of the Sperry Tree Care Spring 2026 Meta Lead Generation campaign — covering what was built, every error encountered and resolved, Rob's feedback alignment, the full technical stack, and the locked-in playbook for future builds.
Campaign scope: 3 ad variations, dual image formats (1:1 Feed + native 9:16 Stories/Reels), Lane + Benton County geo targeting (29 zip codes), GET_QUOTE CTA with lead form, audience 35–65+. Hosted approval page on Cloudflare Workers.
Automated checks run against the live worker URL immediately after final deploy.
| Check | Result | Notes |
|---|---|---|
| Age Range 35–65+ | ✓ PASS | Fixed unicode em-dash patch — previously displayed 25–65 |
| GET_QUOTE CTA | ✓ PASS | Updated from default; all 3 ad creatives recreated with correct CTA |
| Lane County zip codes (97401) | ✓ PASS | 23 Lane County zips locked in adset |
| Benton County zip codes (97330) | ✓ PASS | 6 Benton County zips locked in adset |
| Native 9:16 Stories image | ✓ PASS | Vertical images generated with Imagen 4 Ultra, not stretched crops |
| 1:1 Feed image | ✓ PASS | Square format shown for Feed placements |
| 1.91:1 Right Column image | ✓ PASS | Landscape format shown for desktop placements |
| Ad copy present | ✓ PASS | All 3 versions pulled verbatim from live Meta API |
| Checklist section | ✓ PASS | Pre-launch checklist included on approval page |
| Page title correct | ✓ PASS | "Sperry Tree Care — Meta Campaign Approval" |
| No orphan warning reference | ✓ PASS | "see ⚠️ above" removed — was dangling after warning deleted |
| 3 ad variations | ✓ PASS | 6 "Version" refs = 3 ads × 2 (title + label); pattern too strict |
| Rob approval section | ✓ PASS | Approval CTA and instructions present |
Every error, misstep, and rework in chronological build order — with root cause and fix applied.
/tmp is session-scoped on Desktop Commander. File /tmp/meta-approval-v3.js built in session 1 was gone when session resumed. Full rebuild required — wasted ~90 min./Users/Jason/Documents/ immediately on creation. Never rely on /tmp for files that may be needed later.{"key": "97401"}. Meta API requires country prefix: {"key": "US:97401"}. All 29 zip codes had to be reformatted and resubmitted.US:XXXXX format. Pre-validate format before API call.INVALID_AGE_MAX error. Platform cap is 65 regardless of input. Took 2 API round trips to discover. Workaround: omit age_max entirely — Meta interprets as 35–65+ (no upper limit).object_story_spec only. Never attempt asset_feed_spec when using lead gen CTAs.hashes[] array param key even when uploading a single image.hashes[] param key for image hash submission. Documented in API gotchas.json.dumps() serializes "–" as \u2013. The patch script used literal string 25–65 which didn't match 25\u201365 in the JS file. Live page showed wrong age for one full deploy cycle.lead_gen_form_id was not set on creative build. All 3 creatives had to be deleted and recreated.Each piece of client feedback, its root cause, resolution, and current status.
| Feedback | Root Cause | Resolution | Status |
|---|---|---|---|
| "door on truck" | Imagen prompt too loose on vehicle details; model hallucinated truck door | Regenerated all 3 images with Imagen 4 Ultra; explicit composition prompts for vehicle accuracy | ✓ Resolved |
| "blurred edge frames and employees heads getting cut off" | 1:1 images center-cropped/edge-blurred to fill 9:16 slots — wrong approach for Stories | Generated native 9:16 (1080×1920) images; vertical-specific composition; Sperry logo composited lower-left | ✓ Resolved |
| "should be Get Quote" | Initial creatives used default CTA; GET_QUOTE not wired on creation | Recreated all 3 ad creatives with object_story_spec + GET_QUOTE + lead_gen_form_id |
✓ Resolved |
| "see ⚠️ above — there is no warning triangle above" | Warning removed but placement table reference not updated | Rebuilt page section; orphan reference removed in v5 rebuild | ✓ Resolved |
| "age range should be 35–80" → "35–65+" | age_max defaulted to 25 in initial build; Meta hard cap 65; then unicode patch missed encoded form | Set age_min=35, omitted age_max (= 35–65+ in Meta); fixed unicode patch to target \u2013 encoded form |
✓ Resolved |
| "still says 25–65" | Literal patch didn't match JSON-serialized unicode em-dash \u2013 |
Python patch targeting encoded form; confirmed 2 occurrences replaced; redeployed; live QA verified | ✓ Resolved |
| Item | Value |
|---|---|
| API Version | Graph API v25.0 |
| Account ID | act_2556180444815953 |
| Adset ID | 6981816306436 |
| Creative IDs | v1: 1639362623949691 | v2: 987882690492875 | v3: 895671293519348 |
| CTA Type | GET_QUOTE with lead_gen_form_id |
| Token location | .claude/memory/context/api-keys.md |
| Token renewal | developers.facebook.com/tools/explorer |
| Item | Value |
|---|---|
| Type | Local Python MCP server, registered in claude_desktop_config.json |
| Tools | create_campaign, create_ad_set, create_ad, upload_image, get_account_insights, list_campaigns, update_campaign_status, list_ad_images (10 total) |
| Limitation | Does not expose lead_gen_form_id param — GET_QUOTE CTA requires direct API calls |
| Item | Value |
|---|---|
| Model | Imagen 4 Ultra (imagen-4.0-ultra-generate-001) |
| API Key | Stored in .claude/memory/context/api-keys.md |
| Endpoint | https://generativelanguage.googleapis.com/v1beta/models/imagen-4.0-ultra-generate-001:predict |
| Auth | API key in URL query param ?key=... |
| Formats used | 9:16 for Stories/Reels; 1:1 for Feed |
| Logo compositing | PIL/Pillow — lower-left, 18% width, y≈72% height |
| Logo source | .../Sperry Tree Care/Creative and Logos/cropped-Sperry_Tree_Care_logo_2025-small-fullcolor.png |
| Item | Value |
|---|---|
| Worker name | sperry-meta-approval-apr2026 |
| Live URL | sperry-meta-approval-apr2026.jason-8ce.workers.dev |
| Current Version ID | 8aba6891-622f-451d-aac1-9ed5c9f2b604 |
| Worker size | ~304KB (base64-embedded images) |
| Deploy method | wrangler CLI v4.67.0 via Desktop Commander (CF MCP not used for deploys) |
| Deploy command | cd /Users/Jason && wrangler deploy /path/file.js --name worker-name --compatibility-date 2024-01-01 |
| Auth | OAuth via ~/.wrangler/config/default.toml |
| Tool | Role | Notes |
|---|---|---|
| Desktop Commander | Mac-side execution: file I/O, wrangler deploys, curl, Python scripts | Primary execution layer for all Mac operations |
| mcp__workspace__bash | Linux sandbox: image processing, data manipulation, API calls | ⚠️ /tmp is session-scoped — not persistent across sessions |
| Meta Marketing MCP | Campaign creation, ad management (read/write) | No lead_gen_form_id support; use direct API for GET_QUOTE |
| PIL/Pillow | Image processing, logo compositing, format conversion | Installed at system Python |
| Item | Value |
|---|---|
| Total zip codes | 29 (23 Lane County + 6 Benton County) |
| Required format | {"key": "US:XXXXX"} — country prefix mandatory |
| Lane County | 97401, 97402, 97403, 97404, 97405, 97408, 97477, 97478, 97448, 97424, 97426, 97439, 97487, 97431, 97455, 97452, 97463, 97437, 97438, 97461, 97488, 97489, 97492 |
| Benton County | 97330, 97331, 97333, 97370, 97456, 97324 |
| Area | Issue | Recommendation | Priority |
|---|---|---|---|
| File persistence | /tmp lost between sessions; caused full approval page rebuild (~90 min lost) | Immediately copy all generated JS/HTML to /Users/Jason/Documents/ on creation. Rule: if a file took more than 5 min to build, it goes to Documents. |
🔴 Critical |
| Image QA gate | Generated images uploaded to Meta without visual review; truck door hallucination reached client | Mandatory human review before upload: generate → save → view → approve → upload. No auto-upload. | 🔴 Critical |
| Meta API validation | Zip code format and age_max errors required multiple API round trips to diagnose | Pre-validate all inputs before API call: zip format US:XXXXX check, age_max ≤ 65 check, CTA compatibility matrix check | 🟡 High |
| Approval page template | Built from scratch each campaign; repeated boilerplate work | Lock this build as the standard CF Worker template. Future campaigns populate placeholders only — no structural rebuild. Skill created for this. | 🟡 High |
| Image brief template | Ad hoc prompts produced hallucinations; required regeneration | Standardized image brief template: platform + aspect ratio (1:1 AND 9:16 separately) + composition rules + brand elements + explicit NO list (no blurred edges, no cropped faces) | 🟡 High |
| CF deploy policy | CF MCP tokens expire mid-session; caused multiple silent failures | Document in CLAUDE.md: CF MCP = read only. All deploys = wrangler CLI via Desktop Commander. No exceptions. | 🟡 High |
| Worker fetch pattern | curl returns multipart; deploy of raw response fails with parser error | Document standard multipart stripping procedure in CLAUDE.md. Add to skill reference. | 🟠 Medium |
| Image storage | Generated images in /tmp only; lost after session | Save all generated images to Google Drive at creation: .../Sperry Tree Care/Campaigns/[campaign]/images/ |
🟠 Medium |
| Pre-deploy QA automation | Manual checks only; some issues caught post-deploy | Run automated QA script against live URL after every deploy. Fail fast. Check: age range, CTA text, zip count, no orphan refs, all 3 ad sections present. | 🟠 Medium |
| Unicode-aware patching | Literal string patch missed JSON-encoded em-dash; required 2 deploy cycles to fix | All CF Worker patches use Python with explicit grep for actual encoded form before replacement. Verify match count > 0 before writing. | 🟠 Medium |
Locked standard process for Meta lead gen campaigns. Follow in order. Each phase has a time estimate.
Service area, audience (age range, interests), imagery style, CTA type, daily/lifetime budget, campaign objective, lead form ID
Format as {"key": "US:XXXXX"} arrays from service area definition. Verify coverage map.
Check Meta token at developers.facebook.com/tools/explorer. Verify Imagen API key in .claude/memory/context/api-keys.md. Confirm Meta account ID and lead form ID.
For EACH ad variation: specify platform, both aspect ratios (1:1 AND 9:16 separately), subject composition, brand elements (logo placement), explicit prohibitions (no edge blur, no cropped faces, no incorrect vehicle details)
Imagen 4 Ultra. Save to .../Sperry Tree Care/Campaigns/[campaign]/images/ immediately — not /tmp only.
Separate Imagen 4 Ultra call with vertical-specific composition prompt. Subject must be visible in full frame with no edge blurring. Composite Sperry logo: lower-left, 18% width, y≈72%.
View all generated images before uploading. Check: no hallucinations, correct composition, logo visible, brand accurate. Do not proceed until images are approved.
Via Meta MCP or direct API. Objective: LEAD_GENERATION.
age_min=35, omit age_max (= 35–65+). Zip targeting with US: prefix. Placements: Advantage+ Auto.
Use hashes[] param key (even single image). Save all hash values for creative build.
Use object_story_spec ONLY. Set GET_QUOTE CTA + lead_gen_form_id. Never use asset_feed_spec with lead gen CTAs.
Pull each creative, adset, and ad via API. Confirm: CTA, copy, targeting, image hash, status=PAUSED.
Use the meta-social-campaign skill. Populate placeholders: campaign name, targeting summary, ad copy, zip codes, image previews (base64 embed).
For each of the 3 ad variations: show 1:1 Feed, 9:16 Stories, 1.91:1 Right Column previews with dimensions labeled.
Grep for: age range correct, CTA text, zip count, "see above" orphan refs, all 3 ad sections present. Fix before deploying.
wrangler deploy via Desktop Commander. Save JS to /Users/Jason/Documents/ immediately. Run automated QA on live URL after deploy.
{"key": "US:XXXXX"} — country prefix mandatory. Plain zip rejected.object_story_spec. Never combine with asset_feed_spec (error 1443048).hashes[] param key even for single image. Plain string rejected.\u2013. Literal patches won't match. Use Python targeting the raw encoded form.This campaign build is locked as the standard for all future Meta social campaign builds. The meta-social-campaign skill encodes this playbook, the API gotchas, the approval page template, and the image generation workflow.
Skill file saved to: /Users/Jason/Documents/meta-social-campaign-SKILL.md
To install: move to /var/folders/.../claude-hostloop-plugins/.../skills/meta-social-campaign/SKILL.md or package as a Cowork plugin.
The skill triggers automatically — just describe what you need in plain English:
build a Meta campaign for [client]create an approval page for [client] Facebook adsset up a Meta lead gen campaign for [client]generate ad images for a Meta campaignTo force-load it explicitly regardless of phrasing, use /meta-social-campaign as a slash command in Cowork.