← back to dashboard

blind-bag-spells (FunSpellPanda) — Fix-Ready Issue

_Filed: 2026-07-12 · Re-verified: 2026-08-04 · Diagnosis: "Not auto-healing (fixable=false, autoHeal=true)" · Verify verdict: {"app":"blind-bag-spells","isRealFix":false,"confidence":"high","reasoning":"No fix attempted","risks":""}_

Status (one line)

Unhealthy: App Store state is REJECTED (App Review launch crash, two ASC-console metadata items, and the attached build has expired), and it was NOT auto-healed because the doctor classified the blocker as fixable=false at whole-app granularity while autoHeal=true — so no fix was ever attempted (verifier: "No fix attempted"), even though the underlying data bug is trivially fixable.


Last Known-Good Build Number

Build 11 — from /Users/zzxwill/Programming/go/src/zzxwill/apple-apps/apple-apps-fleet/state/asc.json (generated_at: 2026-08-03T19:15:04Z):

"blind-bag-spells": {
  "bundle_id": "com.zhouzhengxi.blindbagspells",
  "asc_id": "6763868165",
  "asc_name": "FunSpellPanda",
  "in_prod": true,
  "latest_build_number": "11",
  "latest_build_state": "VALID",
  "latest_uploaded": "2026-06-16T06:37:02-07:00",
  "build_count": 11,
  "app_store_state": "REJECTED",
  "version_string": "1.0.0"
}

That is build 11, state VALID, uploaded 2026-06-16, version 1.0.0. "Known-good" here means only *accepted by ASC upload processing* — it is not review-approved (app_store_state: REJECTED).

> Numbering caveat — read before building. Local fleet state (state/fleet.json) records pending_review_build: 13, but build 13 was only ever built and held locally (last_upload_status: "held_for_review"); it never reached ASC, which still tops out at 11. project.yml line 10 also says CURRENT_PROJECT_VERSION: "13". Pick the next build number above the ASC maximum (11) *and* above the local maximum (13) — i.e. 14 — so nothing collides.


Root Cause & Evidence

Root cause: the app's bundled word list, BlindBagSpells/vocabulary.json, is a symlink to ../../shared-data/vocabulary.json whose target no longer exists (apple-apps/shared-data/ is empty). Per the app's CLAUDE.md, shared-data/vocabulary.json is the intended single source of truth shared with SpellaPanda, and xcodegen bundles the resolved symlink as a resource. With the target gone the build still "succeeds", the resource silently never lands in the bundle, Vocabulary.loadFromBundle() returns [], and the reviewed build (1.0.0 build 5, which predates today's empty-pool guard) crashed on the Play screen — producing Apple's automated launch-crash rejection. The doctor saw an App-Review rejection (a state it cannot clear on its own) and marked the whole app fixable=false, so auto-heal was skipped along with the mechanical part.

Failing log line (Apple, automated — quoted verbatim from state/asc-review.jsonapps["blind-bag-spells"].message.thread[2].body):

> "App Review Guideline Issue
>
> This is an automated message. The review of this submission cannot proceed. See below for more information.
>
> The app crashed after the initial launch. Apps that crash negatively impact users.
>
> Test the app on supported devices to identify and resolve crashes and stability issues before resubmitting for review.
>
> Learn more about testing a release build."

Latest Apple message on the thread (message.thread[1].body):

> "Hello,
>
> The issues we previously identified still need your attention. … Submission ID: 9f71bdba-9903-49dc-9495-dfcb6279e65c · Review date: April 27, 2026 · Review Device: iPad Air 11-inch (M3) and iPhone 17 Pro Max · Version reviewed: 1.0.0 (5)"
>
> "Guideline 2.3.6 - Performance - Accurate Metadata … The content description selected for the app's Age Rating indicates that the app includes In-App Controls. However, we were unable to find either Parental Controls or Age Assurance mechanisms in the app."

Secondary failing line from the fleet runner (state/runs/launchd-blindbagspells.err):

> fleet-build: missing /Users/zzxwill/Programming/go/src/zzxwill/apple-apps/blind-bag-spells/.fleet/config.sh

(note the path — it points at the stray apple-apps/blind-bag-spells/ directory, which contains only an empty BlindBagSpells.xcodeproj, not the real source at apple-apps/apps/blind-bag-spells/.) The same log also shows repeated jq: parse error: Expected separator between values at line 14, column 12.

Evidence re-verified firsthand in the working tree today (2026-08-04):

  1. Broken data symlink (still live).
  1. The held build 13 IPA ships without the word list. Do NOT publish it. build/export/BlindBagSpells.ipa (605822 bytes, exported 2026-06-30); unzip -l … | grep -ic vocab0.
  2. BlindBagSpells/Vocabulary.swift loadFromBundle() (lines 35–47) returns [] when the resource is missing; the assertionFailure is compiled out of Release, so the failure ships silently.
  3. BlindBagSpells/PlayView.swift HAS since been hardened — line 87 guard !pool.isEmpty else { return Self.fallbackWord } (fallback declared line 83). A new build would therefore no longer crash, but would be degraded to the single fallback word "spell". The reviewed build 5 predates this guard, hence the crash.
  4. The Guideline 2.1(a) Sign-in-with-Apple fix is present in source (BlindBagSpells/AuthStore.swift: line 23 guard error == nil else { return }; the .failure branch at lines 35–43 sets lastError = nil and returns). Keep it.
  5. project.yml line 9 MARKETING_VERSION: "1.0.0", line 10 CURRENT_PROJECT_VERSION: "13".
  6. Local fleet state (state/fleet.json): last_upload_status: "held_for_review", pending_review_build: 13, pending_review_since: 2026-06-29T20:52:25Z, last commit built d76e93b. Latest run log (state/runs/blind-bag-spells-2026-06-29T20-50-55Z.log) ends BUILD READY — held for your review (build 13).
  7. Submission 9f71bdba-9903-49dc-9495-dfcb6279e65c still UNRESOLVED_ISSUES, submitted 2026-04-27T15:07:21.108Z. checks reports 2 blocking errors: "build is expired" (remediation: "Select a non-expired build for this version in App Store Connect") and "Latest review submission has unresolved issues in App Review". nextAction: "Select a non-expired build for this version in App Store Connect".

Two corrections to earlier revisions of this file

  1. The launch crash is already patched; the data bug is not. Because PlayView.swift:87 now guards the empty pool, a rebuild without the data fix would *pass* review while being functionally empty — every round shows only the fallback word "spell". That is arguably worse than crashing, because it ships. Fix the data; do not rely on the guard.
  2. Commit d76e93b ("suppress benign Sign in with Apple failures … auto-healed by fleet-doctor") contains no Swift change. Its diff touches only CURRENT_PROJECT_VERSION 11→12 in project.yml and project.pbxproj, despite the commit message describing an AuthStore edit. The actual SIWA fix landed earlier, in c2e4cf9. Guideline 2.1(a) is therefore already satisfied in source; it needs a build carrying it to reach ASC, not another patch.

Non-code rejection items still open in App Store Connect (why part of this is genuinely fixable=false for an agent):


Claude-Ready Fix Prompt

> Goal: Restore the missing vocabulary data so the app actually has words, cut a fresh build (do NOT publish held build 13 — its IPA verifiably lacks vocabulary.json), clear the ASC console items, and resubmit version 1.0.0.
>
> Source directory: /Users/zzxwill/Programming/go/src/zzxwill/apple-apps/apps/blind-bag-spells/
>
> 1. Restore the vocabulary data (this is the root cause — do this first). /Users/zzxwill/Programming/go/src/zzxwill/apple-apps/apps/blind-bag-spells/BlindBagSpells/vocabulary.json is a dangling symlink to ../../shared-data/vocabulary.json. An intact, schema-compatible copy exists at /Users/zzxwill/Programming/go/src/zzxwill/apple-apps/apps/SpellaPanda/vocabulary.json (258 words). Either:
> - Restore the shared source (preferred — it is the documented design in the app's CLAUDE.md, and SpellaPanda depends on it): cp /Users/zzxwill/Programming/go/src/zzxwill/apple-apps/apps/SpellaPanda/vocabulary.json /Users/zzxwill/Programming/go/src/zzxwill/apple-apps/shared-data/vocabulary.json, then confirm the symlink resolves (test -e /Users/zzxwill/Programming/go/src/zzxwill/apple-apps/apps/blind-bag-spells/BlindBagSpells/vocabulary.json); or
> - De-symlink: replace BlindBagSpells/vocabulary.json with a real copy of that file and update CLAUDE.md, which currently documents the shared-data symlink scheme.
> - Also address why it vanished: /Users/zzxwill/Programming/go/src/zzxwill/apple-apps/shared-data/ is not a git repo, so the single source of truth for two shipping apps is untracked and unrecoverable if deleted again. Put it under version control (or vendor a real copy into each app) and state which you chose.
> 2. Prove the file lands in the bundle. After make export (the Makefile is the canonical entry point — do not call xcodebuild directly), run unzip -l /Users/zzxwill/Programming/go/src/zzxwill/apple-apps/apps/blind-bag-spells/build/export/BlindBagSpells.ipa | grep -i vocab and require a hit; then extract it and confirm it decodes to a non-empty words array. This check currently returns 0 matches and is the single most important regression gate here — the whole bug is that the broken symlink silently failed to bundle.
> 3. Keep the existing hardening — verify, do not re-break:
> - /Users/zzxwill/Programming/go/src/zzxwill/apple-apps/apps/blind-bag-spells/BlindBagSpells/PlayView.swift line 87: guard !pool.isEmpty else { return Self.fallbackWord } must stay (fallback declared line 83).
> - /Users/zzxwill/Programming/go/src/zzxwill/apple-apps/apps/blind-bag-spells/BlindBagSpells/AuthStore.swift: line 23 guard error == nil else { return }; the .failure branch (lines 35–43) must stay silent so a cancelled Sign in with Apple shows no error UI.
> - Make the silent failure loud in /Users/zzxwill/Programming/go/src/zzxwill/apple-apps/apps/blind-bag-spells/BlindBagSpells/Vocabulary.swift loadFromBundle() (lines 35–47): assertionFailure is stripped in Release, which is precisely why this shipped unnoticed. Replace it with something that also fails in Release (e.g. fatalError, or preconditionFailure under #if !DEBUG), or add a unit test asserting Vocabulary.all.count > 0 and that both Level.k2 and Level.p1 return non-empty arrays. The test is the cheapest durable guard.
> 4. Fix the fleet runner path error (secondary): /Users/zzxwill/Programming/go/src/zzxwill/apple-apps/apple-apps-fleet/state/runs/launchd-blindbagspells.err ends with fleet-build: missing /Users/zzxwill/Programming/go/src/zzxwill/apple-apps/blind-bag-spells/.fleet/config.sh. The runner points at the stray apple-apps/blind-bag-spells/ directory instead of apple-apps/apps/blind-bag-spells/. Correct the fleet config / launchd plist path, and consider deleting the stray directory. The same log also shows repeated jq: parse error: Expected separator between values lines — check the JSON the runner feeds jq.
> 5. Smoke-test on the reviewer's environment. Review used iPad Air 11-inch (M3) and iPhone 17 Pro Max; version reviewed 1.0.0 (5). On the nearest simulators: cold-launch, open Play, spell several words across multiple bag-opens (confirm real words, not the "spell" fallback), then trigger Sign in with Apple and cancel — no error UI, app stays usable signed-out. (See the test-ios-app skill.)
> 6. Cut a fresh build, numbered 14. ASC's highest build is 11 (last known-good upload, VALID, 2026-06-16) but a local build 13 was already produced and held, and project.yml line 10 already reads CURRENT_PROJECT_VERSION: "13" — so bump to "14" to clear both. Keep project.yml and BlindBagSpells.xcodeproj/project.pbxproj in sync, run make generate, then build/export. Do NOT publish the currently held build 13 IPA. A fresh upload is required anyway: ASC reports the currently attached build as expired.
> 7. Clear the ASC console items (no code): App Information → Age Rating → Parental Controls = None (2.3.6); confirm the kids-data-practices reply (2.1 / 1.3) was acknowledged and re-send if not; confirm App Privacy declarations state no data collected / on-device only; select the new non-expired build for version 1.0.0.
> 8. Attach the new build to the 1.0.0 submission (submission 9f71bdba-9903-49dc-9495-dfcb6279e65c, state UNRESOLVED_ISSUES) and resubmit for review.


Process note (worth fixing separately)

This app has produced ~13 near-identical re-diagnoses over three weeks with zero convergence. fixable=false is computed per-app rather than per-blocker, so an ASC-console blocker vetoes the code-level fix in the same app. Splitting that classification would let the symlink restore proceed automatically while the console items stay queued for a human. Related: fleet-doctor.sh never reads pending_review_since, so builds waiting on a human render as unexplained reds.

_No app code was changed while filing this. Evidence re-verified firsthand against the working tree, state/asc.json, state/asc-review.json, and the held IPA._