ios-log-diagnose
GitHub自动化诊断 iOS 剪贴板同步故障。通过驱动模拟器并读取 OSLog,分析 Swift 壳层与 Rust 核心的同步决策及错误路径,替代人工收集日志,用于复现和排查移动端同步 Bug。
触发场景
安装
npx skills add UniClipboard/UniClipboard --skill ios-log-diagnose -g -y
SKILL.md
Frontmatter
{
"name": "ios-log-diagnose",
"description": "Drive the UniClipboard iOS app in a simulator and read its OSLog yourself to diagnose a mobile-sync bug, instead of asking the user to paste logs. Use when debugging why the iOS sync engine or M5 reducer did something, reproducing an iOS sync \/ clipboard bug on the simulator, or whenever you'd otherwise ask the user \"what do the logs say\". All output is Swift OSLog under subsystem `app.uniclipboard` — the Rust core (uc-mobile) emits no logs of its own; the Swift shell logs the reducer's decisions."
}
ios-log-diagnose
Read the UniClipboard iOS app's logs yourself. The mobile-sync decision core lives in Rust (uc-mobile reducer), but it has no logger — the Swift SyncEngine shell logs every reducer decision and outcome to OSLog. So one OSLog stream shows both the native Swift logic and the Rust core's behavior.
iOS app repo: /Users/mark/MyProjects/iOSApp/UniClipboard. The reducer-path log harness lives in UniClipboard/Sync/SyncEngine.swift (commit e94ffd1).
The helper is .agents/skills/ios-log-diagnose/ios-logs.sh — the only thing to invoke. Don't hand-roll simctl/log pipelines unless it can't express what you need (macOS has no timeout; the script wraps log stream in a perl alarm — reproducing that by hand is the usual mistake).
Two channels — pick by what you're after
Log level decides where a line goes. This split is the whole mental model:
debug→ live only, never persisted. The per-tick decision trace:sync preamble: proceed/stop(...),sync route: converged/server-new/push(...), push silent-skip. Seen ONLY by streaming while it happens. Use to watch what the reducer decides each tick.notice/error→ persisted, queryable after the fact. The event trail:sync apply/stage/push/consent-push,sync history: round done, the fivehandle_*transitions, andtick: SyncError .... Use to reconstruct what already happened.
So: streaming a reproduction answers "what is it deciding right now"; show answers "what happened in the last N minutes".
Steps
-
Drive — boot a sim, install the newest build, inject the Rust-core flag ON + an active server, launch:
.agents/skills/ios-log-diagnose/ios-logs.sh drive [SERVER_URL]The engine only ticks with an active server, so
drivealways injects one. A dead URL (the default) still exercises preamble → proceed →getClipboardfails →tick: SyncError→ backoff — enough to see decisions + the error path. For the happy path (route/apply/push/converge), pass aSERVER_URLthat actually returns data (a runninguniclipdmobile-sync server). Completion: the command printsinjected: flag=ON server=...and a launch PID. -
Read — choose the channel:
.agents/skills/ios-log-diagnose/ios-logs.sh stream [SECONDS] [CATEGORY] # live debug, default 15s .agents/skills/ios-log-diagnose/ios-logs.sh show [DURATION] [CATEGORY] # persisted notice/error, default 5mCATEGORY(optional):sync(SyncEngine — reducer + tick; the usual one),network(HTTP client / connect-uri),store(persistence),app,intents. Omit for every category underapp.uniclipboard. Completion: you have thesync(or target) lines for the run you triggered, and can name what the reducer decided / what failed.
Gotchas
- No content, ever. Logs carry states / bools / counts / decision enums plus an 8-char
hashTagprefix — a one-way SHA fingerprint that correlates one clipboard item across pull → stage → apply → push → converge, never the content. - Flag injection is forward-safe.
drivewritesmobileCore.syncClientUsesRustCore = YES. Once the native A/B paths are deleted and Rust is the only path, that key just goes unread — the script keeps working unchanged. - App Group, not the app sandbox. Flag + server live in
defaultssuitegroup.app.uniclipboard.UniClipboard; injecting them needs aterminate+launchsoloadServersre-reads.drivehandles this. - More injection hooks exist.
grep ProcessInfo.processInfo.environmentin the iOS repo forUC_*(e.g.UC_DEVICE_TEXTto seed a device copy,UC_TEST_QR_PAYLOADto add a server via connect-uri) — passed throughSIMCTL_CHILD_<NAME>=valueon launch. - Build first if
drivesays "no built UniClipboard.app":xcodebuild -scheme UniClipboard -sdk iphonesimulator -destination 'generic/platform=iOS Simulator' build CODE_SIGNING_ALLOWED=NO.
版本历史
- c6483d7 当前 2026-11-13 18:20


