5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

M1 Macの「torne® mobile」で購入の復旧が永遠に固まる問題を、レシートのパーミッションから直した話

5
Posted at

iOS/iPadOSアプリを Apple Silicon Mac で動かしたときに踏みやすい、StoreKitレシートのパーミッション破壊バグの実例と対処です。torne® mobile を題材にしていますが、原因と診断手順は「Mac App Storeから入れたiOSアプリで購入(IAP)が復元できない」系のトラブル全般に応用できます。

TL;DR

  • 症状: M1 Mac版 torne® mobile で「購入情報の復旧」を押しても固まる/反映されない。視聴・書き出し機能が有効にならない。
  • 真因: storekitagent がレシートを書き込むたびに、レシートファイルのパーミッションが壊れた値(--x-wx--T / mode 1130)になり、アプリ自身がレシートを読めなくなっていた。App Store側の復元処理は毎回成功しているのに、書き込まれたレシートが読めないので反映されない、という状態。
  • 対処: レシートを chmod 600 で直す → ただしOSが復元のたびに壊れたモードで上書きするので、0.2秒間隔のchmodウォッチャーを回しながら操作する → アプリを再起動 → カート画面で「購入情報の復旧」だけを押す(650円の購入ボタンは押さない)。

環境: macOS 26.5.1 / Apple M1 / Mac App Store経由でインストールした iOSアプリ torne® mobile。


何が起きていたか

torne® mobile はもともと iOS/iPadOS 用アプリで、Apple Silicon Mac では「Mac App Storeから入れられるiOSアプリ」として動きます。買い切りの機能課金(IAP)が2つ入っていて、レシートには以下が両方記録されていました。

  • torne.function.play … 視聴再生(2019-06購入)
  • torne.function.move … 書き出し(2019-05購入)

つまり購入そのものは生きている。App Storeの「購入情報の復旧」も、ログを見るかぎり毎回成功している。にもかかわらずアプリ側で機能が有効にならない——ここが最初の謎でした。

真因:レシートのパーミッションが書き込みのたびに壊れる

システムログを見ると、アプリ起動時にこう吐いていました。

Failed to parse receipt ... Permission denied

レシートの実体はこの場所にあります(UUIDはコンテナごとに異なります。Finder上の表示名は「torne® mobile」)。

~/Library/Containers/<コンテナUUID>/Data/StoreKit/receipt

このファイルのパーミッションを確認すると、正常なら rw-------(600)であるべきところが、

--x-wx--T   (mode 1130)

という壊れた値になっていました。storekitagent がレシートを書き直すたびにこのモードになり、アプリ自身が自分のレシートを読めない。他アプリのレシートは正常に rw------- なので、このコンテナ特有の現象(macOS 26.5.1時点)でした。

ポイント: 「復元は成功しているのに反映されない」という症状の裏で、書き込み直後のパーミッションが読み取り不能になっていた、というのが核心です。


診断に使ったコマンド

同種のトラブルを切り分けるときに役立った手段をまとめておきます。

1. StoreKit側のログを見る

# 注意: log はそのまま打つと zsh に解釈されることがあるのでフルパス推奨
/usr/bin/log show --predicate 'process == "storekitagent"' --last 1h

Failed to parse receipt ... Permission denied のような行が出ていれば、レシートが読めていないサインです。

2. クラッシュレポートを見る

ls -lt ~/Library/Logs/DiagnosticReports/torneplus-*.ips
# Retired/ 配下にもある
ls -lt ~/Library/Logs/DiagnosticReports/Retired/torneplus-*.ips 2>/dev/null

.ips の中の faultingThread(落ちたスレッド名)で分類できます。

3. レシートの中身を直接確認する

レシートは PKCS#7 署名のみで暗号化はされていないので、IAPのproduct idを直接読めます。

strings -n 6 "<receiptのパス>" | grep -i "torne.function"
# torne.function.play / torne.function.move が見えれば購入自体は生きている

直し方(再発したときの手順)

手順0: 自分のコンテナUUIDを特定する

# StoreKit/receipt を持つコンテナを探す
find ~/Library/Containers -maxdepth 4 -path "*StoreKit/receipt" 2>/dev/null | while read -r r; do
  if strings -n 6 "$r" 2>/dev/null | grep -q "torne.function"; then
    echo "$r"
  fi
done

出てきたパスが、あなたの環境のレシートです。以降このパスを使います。

手順1: chmodウォッチャーを回しながらレシートを直す

OSが復元操作のたびに壊れたモードで上書きするため、1回 chmod 600 するだけでは間に合いません。0.2秒間隔で常時600に直し続けるウォッチャーをバックグラウンドで回します。

#!/bin/bash
# torne_receipt_watch.sh
# StoreKitレシートのパーミッションを 600 に保ち続ける(torne mobile 購入復旧用)
# 使い方:
#   1) RECEIPT を自分のパスに置き換える(手順0で特定したもの)
#   2) chmod +x torne_receipt_watch.sh && ./torne_receipt_watch.sh
#   3) アプリ側で「購入情報の復旧」が反映されたら Ctrl-C で停止

RECEIPT="$HOME/Library/Containers/<コンテナUUID>/Data/StoreKit/receipt"

if [ ! -e "$RECEIPT" ]; then
  echo "レシートが見つかりません: $RECEIPT"
  echo "手順0のfindコマンドでパスを確認してください。"
  exit 1
fi

echo "watching: $RECEIPT  (Ctrl-C で停止)"
while true; do
  # 壊れたモードを検出したら 600 に戻す(差分があるときだけ表示)
  mode=$(stat -f "%Lp" "$RECEIPT" 2>/dev/null)
  if [ "$mode" != "600" ]; then
    chmod 600 "$RECEIPT" 2>/dev/null && echo "$(date '+%H:%M:%S') fixed: $mode -> 600"
  fi
  sleep 0.2
done

stat -f "%Lp" は macOS(BSD系)の書式です。Linuxの stat -c %a とは違うので注意。

手順2: アプリを再起動する

ウォッチャーを回したまま、torne® mobile を一度終了して起動し直します。読めるレシートがある状態で起動させるのが狙いです。

手順3: 「購入情報の復旧」だけを押す

カート(課金)画面で 「購入情報の復旧」だけ を押します。

⚠️ 650円の購入ボタンは押さないこと。 iOSアプリ on Mac の既知パターンで、購入経路はシステム確認ダイアログが出ずに必ず固まります。復旧ボタンは正常に動きます。

ウォッチャーが効いていれば、復元で書き直されたレシートも即座に600へ直され、アプリが読めるようになって機能が有効化されます。反映を確認したら、ウォッチャーは Ctrl-C で止めて構いません(購入状態はアプリ側に永続化されます)。


効かなかった対策(試さなくてよい)

遠回りした分も共有します。以下は単独では無効でした。

  • フォルダへの file_inherit ACL付与 … OSはレシートを rename(移動) で配置するため、ディレクトリのACLが継承されない。
  • コンテナの完全リセット(Data削除→初期設定やり直し)… 書き直されるレシートがまた壊れるので、リセット単独では解決しない。
  • Wi-Fi/有線の二重接続の解消 … クラッシュとは無関係だった(ネットワークを一本化すること自体は無害)。

おまけ: 直っても散発するクラッシュについて

UPnP探索スレッド PvrUpnpAdvertiseReceiver の SIGSEGV(null + 0x970)が散発します。LAN上のSSDP通知が引き金で、Wi-Fiをオフにしても発生することがあります。起動数分後に落ちる場合がありますが、購入状態はアプリ側に永続化済みなので致命的ではありません。


まとめ

  • 「復元は成功しているのに反映されない」ときは、レシートファイルのパーミッションを疑う。
  • Apple Silicon Mac の iOSアプリは、storekitagent の挙動でレシートが読めなくなることがある。
  • 対症療法としては chmodウォッチャー + 復旧ボタンのみ が効く。購入ボタンは固まるので押さない。
  • 診断は log show(storekitagent)/ .ips クラッシュレポート / strings でのレシート中身確認、の3点セット。

同じ症状で詰まっている人の助けになれば。


5
4
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?