---
phase: 20.2-sync-integrity-and-shipment-state-fixes
plan: 03
subsystem: database
tags: [rust, sqlite, github-issues, sync]

# Dependency graph
requires:
  - phase: 17-product-catalog
    provides: GhIssuesClient, upsert_product, on_lookup_create_confirmed callback structure
  - phase: 18-ww-card-gh-issues
    provides: set_card_issue_number pattern used for set_product_issue_number

provides:
  - set_product_issue_number() on SqliteStore — writes github_issue_number for a product after GH Issue creation
  - Synchronous ww-product GH Issue creation from on_lookup_create_confirmed (D-08 enforced)

affects: [sync-integrity, product-catalog, stale-product-cleanup]

# Tech tracking
tech-stack:
  added: []
  patterns:
    - "D-08 (LOCKED): GH Issue creation joined synchronously via thread::spawn(...).join() to prevent stale-product cleanup race"
    - "set_product_issue_number mirrors set_card_issue_number / set_unit_issue_number — never overwrites via upsert"

key-files:
  created: []
  modified:
    - crates/app/src/main.rs
    - crates/service/src/db/sqlite.rs

key-decisions:
  - "D-08 (LOCKED): ww-product GH Issue creation in lookup modal is synchronous — thread joined before callback returns"
  - "set_product_issue_number uses targeted UPDATE (not upsert) so sync never overwrites the issue number"
  - "Error path leaves github_issue_number = None — stale cleanup only deletes products WITH an issue number, so this is safe"

patterns-established:
  - "set_product_issue_number: separate UPDATE function for issue number write-back, matching set_card_issue_number and set_unit_issue_number pattern"

requirements-completed: [D-08]

# Metrics
duration: 8min
completed: 2026-04-13
---

# Phase 20.2 Plan 03: Synchronous Lookup Modal Product GH Issue Creation Summary

**ww-product GH Issue creation in on_lookup_create_confirmed now blocks via thread join, writing github_issue_number to SQLite before the callback returns — eliminating the stale-product cleanup race (D-08)**

## Performance

- **Duration:** ~8 min
- **Started:** 2026-04-13T11:14:00Z
- **Completed:** 2026-04-13T11:22:01Z
- **Tasks:** 1
- **Files modified:** 2

## Accomplishments

- Replaced fire-and-forget `thread::spawn` in `on_lookup_create_confirmed` with a joined thread so GH Issue creation is synchronous
- Added `set_product_issue_number()` to `SqliteStore` matching the `set_card_issue_number` / `set_unit_issue_number` pattern
- Issue body format confirmed to match `on_add_product_submit` exactly (`schema_version`, `product_id`, `image_url`, `shopify_product_url`)
- Error path documented: product stays with `github_issue_number = None`, safe because stale cleanup only deletes products that already have an issue number

## Task Commits

Each task was committed atomically:

1. **Task 1: Make lookup modal product GH Issue creation synchronous** - `425f118` (feat)

**Plan metadata:** (docs commit follows)

## Files Created/Modified

- `crates/app/src/main.rs` - Replaced fire-and-forget spawn with joined thread in `on_lookup_create_confirmed`; switched to `set_product_issue_number` for targeted issue number write-back
- `crates/service/src/db/sqlite.rs` - Added `set_product_issue_number()` function (targeted UPDATE, never overwrites via sync upsert)

## Decisions Made

- Used `thread::spawn(...).join()` rather than moving all I/O inline on the UI thread — this keeps the GH API call off the main execution stack while still blocking the callback return until the issue number is written
- Used `set_product_issue_number` (targeted UPDATE) instead of `upsert_product` for the write-back, consistent with the established pattern for card and unit issue numbers

## Deviations from Plan

### Auto-fixed Issues

**1. [Rule 2 - Missing Critical] Added set_product_issue_number() to SqliteStore**
- **Found during:** Task 1
- **Issue:** Plan referenced `store.set_product_issue_number()` but the function did not exist in SqliteStore; only `set_card_issue_number` and `set_unit_issue_number` existed
- **Fix:** Added `set_product_issue_number(&self, product_id: &str, number: i64)` using the same targeted UPDATE pattern as its siblings
- **Files modified:** `crates/service/src/db/sqlite.rs`
- **Verification:** `cargo check` passes
- **Committed in:** `425f118` (Task 1 commit)

---

**Total deviations:** 1 auto-fixed (1 missing critical function)
**Impact on plan:** Required for correctness — the plan's implementation depended on this function. No scope creep.

## Issues Encountered

None — `cargo check` passed first attempt after adding the missing function.

## User Setup Required

None - no external service configuration required.

## Next Phase Readiness

- Products created via the lookup modal now have `github_issue_number` written before the next sync cycle can run stale-product cleanup
- D-08 (LOCKED) is fully implemented
- No regression risk: `on_add_product_submit` (product catalog view) was not modified

## Known Stubs

None — no stubs or placeholders introduced.

## Threat Flags

None — no new network endpoints, auth paths, file access patterns, or schema changes at trust boundaries introduced.

## Self-Check: PASSED

- `crates/app/src/main.rs` modified — confirmed via Read
- `crates/service/src/db/sqlite.rs` modified — confirmed via Read
- Commit `425f118` present in git log

---
*Phase: 20.2-sync-integrity-and-shipment-state-fixes*
*Completed: 2026-04-13*
