Engineering notes

Apple's Private Cloud Compute refused 10.6% of my app's requests as unsafe

November 6, 2026 · Liner Notes · 日本語版

I moved diary summaries and a short morning note from OpenAI to Apple's Private Cloud Compute (PCC). Counting 41 days of production logs, 65 of 614 requests (10.6%) were refused as "may contain sensitive or unsafe content," and 59 (9.6%) were rate-limited. What got refused was ordinary diary text and conversation summaries. Here are the failures this caused and how I rebuilt around them.

What I wanted to do

Liner Notes, my iPhone diary app, uses a language model for conversation summaries, a short note every morning, titles for records, and the roles of speakers in a conversation. It started out on OpenAI's API.

At the end of September 2026 I moved these to PCC, which lets an iOS app call Apple's server-side language model through the Foundation Models framework. The app pays nothing, and requests are not retained on Apple's servers. For an app that handles conversations, both are big advantages.

Counting the production logs

I noticed memos with no summary and counted the production logs (my own iPhone, 41 days).

RequestsFailedRefused as unsafeRate-limitedDaily quotaCancelled, other
614130 (21%)65 (10.6%)59 (9.6%)06

Breakdown of 614 production requests (41 days)

Succeeded 484 (78.8%)Refused as unsafe 65 (10.6%)Rate-limited 59 (9.6%)Cancelled, other 6 (1.0%)
Show as a table
ItemValueShare
Succeeded48478.8%
Refused as unsafe6510.6%
Rate-limited599.6%
Cancelled, other61.0%

The refusals came with three messages.

MessageCount
Streamed response may contain sensitive or unsafe content41
Safety guardrail was triggered.18
Response may contain sensitive or unsafe content6

What got refused was diary drafts and conversation summaries, nothing sexual or violent. A summary of a technical meeting was refused too. For Apple's on-device model, developers have repeatedly reported on the forums that it refuses ordinary requests too often. The same thing happens with server-side PCC.

Refusals are also slow. A success comes back in 1–3 seconds; a refusal took 7.6–12.7 seconds, because the response starts streaming and the filter stops it partway through.

Time until a response

Seconds (drawn at the middle of each range)

Success
1–3 s
Refusal
7.6–12.7 s
Show as a table
ItemValue
Success1–3 s
Refusal7.6–12.7 s

The problems this caused

Refusals and rate limits caused four problems in the app.

First, summaries went missing silently. When PCC failed, no summary was made, leaving memos with a transcript but no summary. To users that looks like a bug.

Second, a safety switch shut PCC off in production. I had a switch that disables a generation path if the app crashes in the middle of it. It counted a background task that merely expired as a crash, and on October 16 it turned off PCC entirely in production.

Third, I deleted records while over the daily quota. On the day I regenerated person profiles in bulk on a development iPhone, it made 1,158 requests and 689 of them failed with the quota error. Once the quota is hit, every request fails. The job deleted old records before regenerating them, so once they were deleted they could not be rebuilt, and the records dropped to 7.

Fourth, the morning batch hit the rate limit. The limit kicks in at 8–10 requests in the previous 60 seconds. The morning notes were generated together in the 7 o'clock hour, and in production only 3 of 14 requests in that hour succeeded.

Success rate by hour (production, hours with 10+ requests, JST)

%

0:00
83% (10/12)
7:00
21% (3/14)
8:00
79% (38/48)
9:00
70% (14/20)
10:00
53% (60/114)
12:00
82% (31/38)
13:00
86% (38/44)
14:00
96% (22/23)
16:00
97% (31/32)
18:00
76% (25/33)
19:00
82% (28/34)
20:00
93% (14/15)
21:00
97% (31/32)
22:00
90% (54/60)
23:00
90% (55/61)
Show as a table
ItemValue
0:0083% (10/12)
7:0021% (3/14)
8:0079% (38/48)
9:0070% (14/20)
10:0053% (60/114)
12:0082% (31/38)
13:0086% (38/44)
14:0096% (22/23)
16:0097% (31/32)
18:0076% (25/33)
19:0082% (28/34)
20:0093% (14/15)
21:0097% (31/32)
22:0090% (54/60)
23:0090% (55/61)

The success rate drops at 7:00 and at 10:00, the busiest hour. Both are hours when generation was batched.

Trying to reduce refusals

Input length made no difference between refused and accepted requests (both 873 and 2,174 characters were refused). I could not tell which words trigger a refusal, and I have not found a way to reduce refusals by rewording the prompt. So I treated refusals as something that will happen and changed the design.

What I rebuilt

ProblemChange
Summaries go missing silentlyFall back to the on-device model. Conversations are not sent to OpenAI by policy, so on-device is the only fallback
A missing summary looks like a bugSave the transcript first and add the summary later. Even if refused, the transcript is readable
The safety switch misfiresA background expiration is not counted as a crash
Records deleted while over quotaBulk jobs at most 2–3 times a day; never run delete-then-regenerate while over quota
The morning batch hits the rate limitSpread generation over time
No way to tell which feature is refusedLog the feature, the duration and the failure type (refusal, rate limit, quota) for every request

When you hit the daily quota, the error carries the time it resets (resetDate), and quotaUsage shows what is left. I had not read it and was puzzled that "it still hasn't come back the next morning." The quota is reportedly per iCloud account rather than per app.

Summary

More engineering notes