Android (0.5.212): after a factory reset the setup self-claim uses the PREVIOUS boot's one-time PIN — captured from latest.log — and the node answers 401 auth.claim.pin_invalid, so the with-AI pass ends with the node unclaimed and announce refused 409 node is not owner-bound. Gate run https://github.com/CIRISAI/CIRISAgent/actions/runs/34272658734 (CIRISAgent release/2.11.0 @ 67404f6c0, ciris-server 0.5.203), android-logcat.txt, verbatim with timestamps:
20:29:04.380 python.stdout: Node fold: OWNERSHIP UNCLAIMED — one-time CLAIM PIN: D8NG-N3YE (no-AI setup; claim OK)
…factory reset, app restarts…
20:31:05.107 PythonRuntime: Captured one-time CLAIM PIN from node ownership banner.
20:31:06.111 MainActivity: Ownership CLAIM PIN captured — stopping node-log tail
20:31:07.043 python.stdout: Node fold: OWNERSHIP UNCLAIMED — one-time CLAIM PIN: VP9G-E6X7 (this boot's PIN — printed AFTER the capture)
…the gate's with-AI --launch restarts the app…
20:32:04.594 PythonRuntime: Captured one-time CLAIM PIN from node ownership banner.
20:32:05.859 MainActivity: Ownership CLAIM PIN captured — stopping node-log tail
20:32:19.144 python.stdout: Node fold: OWNERSHIP UNCLAIMED — one-time CLAIM PIN: YY7A-B66Q (this boot's PIN — 15 s after the capture)
20:32:56.373 CIRISApiClient: [claimRemote] POST http://127.0.0.1:4243/v1/setup/claim-remote node_code=CIRIS-V1-AHG6-7V7L-D… cohort=self owner_session=password
20:32:56.409 CIRISApiClient: [claimRemote] Exception: RuntimeException: status=401 Unauthorized body={"error":"claim-remote failed: target rejected the claim (HTTP 401): {\"error\":\"invalid one-time claim PIN\",\"reason_id\":\"auth.claim.pin_invalid\"}"}
20:32:56.420 SetupViewModel: [ORDER] claim_settled claimed=false (exception, session=setup): claim-remote failed: 401 …
Twice in a row the capture lands before the running node prints its banner: MainActivity.tailNodeLogsForOwnership lists latest.log (and siblings) sorted by lastModified and parses the first CLAIM PIN line it meets — after a restart that file still carries the previous boot's banner, _localClaimPin latches it (if (_localClaimPin.value == null)), the tail stops, and CIRISApp.kt's source order (1: banner snapshot, 2: <home>/claim_pin, 3: await banner) never reaches the file because a value is already there.
The PIN is in a file on disk; the log is not a reliable source for it. The node writes the current PIN to the durable <CIRIS_HOME>/claim_pin (0600, JSON {"pin": …}), names that path as claim_pin_file in its setup status, and rotates it per boot; the desktop and iOS runtimes already read it (readClaimPinFromFileIfMissing). Ask: on Android, read <home>/claim_pin at claim time (the node writes it a few seconds after health answers, so on demand at COMPLETE, not at boot) and stop latching a log-derived PIN ahead of it — or drop the log tail entirely on Android now that the file exists there too. A real user reproduces this with one reset + re-setup on a phone (the 20:31:05 capture is exactly that path).
Consequence in the same run: announce bundle: error -- announce returned 409: {"error":"announce (promote owner-binding) failed: owner-binding validation: node is not owner-bound — claim ownership before announcing …"}, i.e. the node was never claimed, so the with-AI setup ends 6/7.
Android (0.5.212): after a factory reset the setup self-claim uses the PREVIOUS boot's one-time PIN — captured from
latest.log— and the node answers401 auth.claim.pin_invalid, so the with-AI pass ends with the node unclaimed and announce refused409 node is not owner-bound. Gate run https://github.com/CIRISAI/CIRISAgent/actions/runs/34272658734 (CIRISAgentrelease/2.11.0@ 67404f6c0, ciris-server 0.5.203),android-logcat.txt, verbatim with timestamps:Twice in a row the capture lands before the running node prints its banner:
MainActivity.tailNodeLogsForOwnershiplistslatest.log(and siblings) sorted bylastModifiedand parses the firstCLAIM PINline it meets — after a restart that file still carries the previous boot's banner,_localClaimPinlatches it (if (_localClaimPin.value == null)), the tail stops, andCIRISApp.kt's source order (1: banner snapshot, 2:<home>/claim_pin, 3: await banner) never reaches the file because a value is already there.The PIN is in a file on disk; the log is not a reliable source for it. The node writes the current PIN to the durable
<CIRIS_HOME>/claim_pin(0600, JSON{"pin": …}), names that path asclaim_pin_filein its setup status, and rotates it per boot; the desktop and iOS runtimes already read it (readClaimPinFromFileIfMissing). Ask: on Android, read<home>/claim_pinat claim time (the node writes it a few seconds after health answers, so on demand at COMPLETE, not at boot) and stop latching a log-derived PIN ahead of it — or drop the log tail entirely on Android now that the file exists there too. A real user reproduces this with one reset + re-setup on a phone (the 20:31:05 capture is exactly that path).Consequence in the same run:
announce bundle: error -- announce returned 409: {"error":"announce (promote owner-binding) failed: owner-binding validation: node is not owner-bound — claim ownership before announcing …"}, i.e. the node was never claimed, so the with-AI setup ends 6/7.