Skip to content

feat(skills): document user-attribute → campaign targeting timing#17

Open
kherembourg wants to merge 3 commits into
mainfrom
feat/campaign-attribute-timing
Open

feat(skills): document user-attribute → campaign targeting timing#17
kherembourg wants to merge 3 commits into
mainfrom
feat/campaign-attribute-timing

Conversation

@kherembourg

Copy link
Copy Markdown
Contributor

Contexte

Suite à un thread support Slack : un client n'arrivait pas à faire matcher une campaign ciblée sur une audience construite sur un attribut custom (campaign AppsFlyer) au premier launch — ça ne marchait qu'à partir de la 2ᵉ session, de façon intermittente.

Test des skills sur la question du thread : l'agent purchasely-sdk-expert répondait déjà à ~80 % comme l'équipe (cause racine + gating allowCampaigns), mais il manquait deux points et une imprécision de mécanisme.

Ce qui manquait

  1. « Rien n'est ré-évalué » : setUserAttribute(...) sauvegarde la valeur (persistée entre sessions) mais ne re-déclenche aucun placement/campaign.
  2. « Marche dès la 2ᵉ session » : la valeur persistée est présente au prochain cold start → la campaign matche enfin (= symptôme « ça a marché une fois »).
  3. Mécanisme corrigé (vérifié dans la source iOS) : avec allowCampaigns(false), le deeplink de trigger est mis en file non résolu ; le match d'audience n'a lieu qu'au perform() déclenché par allowCampaigns(true). Différer corrige donc bien le 1ᵉʳ launch.

Note API : le vrai nom est allowCampaigns (pluriel, default true) — confirmé dans la source iOS/Android.

Changements

Fichier Ajout
references/concepts/user-attributes-targeting.md Section « When an attribute change takes effect (timing) »
references/concepts/campaigns.md Section « Custom-attribute audiences… » + anti-pattern + recette
references/troubleshooting/common-issues.md §7 étendue (symptôme hit-or-miss au 1ᵉʳ launch)
skills/purchasely-sdk-expert/SKILL.md Règle de timing dans « Campaigns »
skills/purchasely-debug/SKILL.md Ligne dédiée dans « Common Fixes Database »
CHANGELOG.md Entrées sous [Unreleased]

Vérification

Re-test de l'agent purchasely-sdk-expert après mise à jour : la réponse couvre désormais non-réévaluation + 2ᵉ session + recette allowCampaigns(false) → set attribut → allowCampaigns(true), avec le mécanisme exact.

🤖 Generated with Claude Code

Setting a user attribute saves the value (persisted across sessions) but
does not re-trigger or re-evaluate any placement/campaign. The value is
applied only on the next placement fetch or campaign trigger, which for
APP_STARTED fires shortly after SDK start. A custom-attribute audience
therefore misses on the first launch and matches from the next session
once the value is persisted — the classic hit-or-miss symptom.

Document the reliable first-launch recipe: allowCampaigns(false) → set
attribute → allowCampaigns(true) (ordering: start → attributes → campaigns).

- user-attributes-targeting.md: new timing section
- campaigns.md: custom-attribute audience section + anti-pattern
- common-issues.md §7: first-launch campaign race symptom/fix
- purchasely-sdk-expert / purchasely-debug skills: timing rule
- CHANGELOG: Unreleased entries

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Jun 23, 2026

Copy link
Copy Markdown

Greptile Summary

This is a documentation-only PR that adds precise timing semantics for setUserAttribute → campaign targeting, addressing a real-world support case where a custom-attribute campaign was intermittently matching only from the 2nd session onward. No production code is changed.

  • Adds a dedicated "When an attribute change takes effect (timing)" section to user-attributes-targeting.md and a matching "Custom-attribute audiences" section with a Swift recipe to campaigns.md, both explaining the root cause (attribute set after APP_STARTED resolves) and the allowCampaigns(false) → set attribute → allowCampaigns(true) fix.
  • Propagates the rule into the two agent skills (purchasely-sdk-expert, purchasely-debug) and the troubleshooting guide, and updates CHANGELOG.md as required by CLAUDE.md.

Confidence Score: 5/5

Documentation-only change with no production code touched; safe to merge.

All six files are Markdown documentation or skill instruction files. The new timing semantics are internally consistent, anchor links resolve correctly relative to each file's location, API names (allowCampaigns, setUserAttribute) match the patterns already established in the repo, and CHANGELOG is updated as required. No executable code was introduced.

No files require special attention.

Important Files Changed

Filename Overview
purchasely/references/concepts/campaigns.md Adds "Custom-attribute audiences" section with root-cause explanation, two-option guidance, and a Swift code recipe. Anti-pattern list extended with a setUserAttribute re-evaluation misconception entry. Content is technically accurate and consistent with existing API patterns.
purchasely/references/concepts/user-attributes-targeting.md Adds timing section explaining that setUserAttribute is not an event, with platform-specific notes for placements vs. campaigns. The simplified Swift snippet omits the safety timeout note, but explicitly cross-references campaigns.md for the full recipe.
purchasely/references/troubleshooting/common-issues.md Appends a Related note to §7 covering the hit-or-miss first-launch symptom, its cause, and the allowCampaigns fix. Content is accurate; the long single-line format is consistent with existing troubleshooting entries.
purchasely/skills/purchasely-sdk-expert/SKILL.md Adds an Attribute timing for custom-attribute audiences bullet with the full mechanism, the symptom (works once / hit-or-miss), the fix, and a directive to load the reference files. Relative paths to references are correct.
purchasely/skills/purchasely-debug/SKILL.md Adds a new row to the Common Fixes Database table covering the hit-or-miss campaign symptom. The fix and relative path to campaigns.md are correct.
CHANGELOG.md CHANGELOG updated correctly under [Unreleased] per CLAUDE.md convention with Added and Changed entries that are factual and user-facing.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant App
    participant SDK as Purchasely SDK
    participant Cache as SDK Disk Cache
    participant Server as Purchasely Server

    Note over App,Server: BEFORE - attribute set too late, first launch miss
    App->>SDK: apiKey(...).start()
    SDK->>Server: Evaluate APP_STARTED trigger (no attribute yet)
    Server-->>SDK: Audience: no match
    App->>SDK: setUserAttribute("appsflyer_last_campaign_id", campaignId)
    SDK->>Cache: Persist attribute value
    Note over SDK: Campaign already resolved, attribute ignored

    Note over App,Server: AFTER - attribute set before trigger resolves, first launch match
    App->>SDK: apiKey(...).allowCampaigns(false).start()
    Note over SDK: APP_STARTED trigger queued, not yet resolved
    App->>SDK: setUserAttribute("appsflyer_last_campaign_id", campaignId)
    SDK->>Cache: Persist attribute value
    App->>SDK: allowCampaigns(true)
    SDK->>Server: Evaluate APP_STARTED trigger (attribute present)
    Server-->>SDK: Audience: match
    SDK-->>App: Campaign displayed
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant App
    participant SDK as Purchasely SDK
    participant Cache as SDK Disk Cache
    participant Server as Purchasely Server

    Note over App,Server: BEFORE - attribute set too late, first launch miss
    App->>SDK: apiKey(...).start()
    SDK->>Server: Evaluate APP_STARTED trigger (no attribute yet)
    Server-->>SDK: Audience: no match
    App->>SDK: setUserAttribute("appsflyer_last_campaign_id", campaignId)
    SDK->>Cache: Persist attribute value
    Note over SDK: Campaign already resolved, attribute ignored

    Note over App,Server: AFTER - attribute set before trigger resolves, first launch match
    App->>SDK: apiKey(...).allowCampaigns(false).start()
    Note over SDK: APP_STARTED trigger queued, not yet resolved
    App->>SDK: setUserAttribute("appsflyer_last_campaign_id", campaignId)
    SDK->>Cache: Persist attribute value
    App->>SDK: allowCampaigns(true)
    SDK->>Server: Evaluate APP_STARTED trigger (attribute present)
    Server-->>SDK: Audience: match
    SDK-->>App: Campaign displayed
Loading

Reviews (1): Last reviewed commit: "feat(skills): document user-attribute → ..." | Re-trigger Greptile

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant