fix(sync): a refused push stays refused, and says why (BEA-403) (#146)

A device whose journal push the hub 403s reported healthy sync between
every pair of remote passes, and never showed the hub's reason for the
refusal — so a user whose device was not registered to their account
re-ran `bdrive login` (which the message told them to), re-checked their
project permissions (write), and had nowhere left to look.

Two causes, both local to the client:

- Cycle recomputed st.Access from scratch at the end of every pass,
  including the daemon's cheap local-only ticks that never reach the hub.
  Three of those run between remote passes, so the daemon alternated
  "read-only on this project" / "access restored; syncing normally" every
  few seconds and `bdrive status` reported OK moments after a refused
  push. Now each leg records its own verdict — pull clears no-access, push
  records read-only or clears it — and a cycle that asked nothing leaves
  the last answer standing.
- The hub's own sentence was summarized into "read-only (pull only)",
  which describes the STATUS CODE. It is the only thing that tells a
  device-registration refusal from a project the user really is a reader
  on. It now rides in SyncState.AccessReason and Result.Reason(), printed
  by `bdrive sync`, `bdrive status` and the daemon log, and dropped unless
  it passes journal.SafeText — hub text reaching a terminal.

The hub's refusal also now names the upgrade: the binding is made by the
login request naming its device, which a CLI older than the gate does not
do, so "run `bdrive login`" alone sent that user in a circle. Hub and CLI
deploy separately, so the skew is the expected state right after the gate
ships.


Claude-Session: https://claude.ai/code/session_01GSHsQU4pBCzKkPyPeXSwTm

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Snow Lee (Sungwon)
2026-08-10 20:33:26 -07:00
committed by GitHub
co-authored by Claude Opus 5
parent 709c40e76c
commit 922886b949
10 changed files with 214 additions and 15 deletions
+17 -6
View File
@@ -206,19 +206,30 @@ touches your files:
```
pending: 3 local change(s) not yet pushed
access: read-only (pull only) — 3 local change(s) stay on this device
reason: this device is not registered to your account on this hub; update bdrive, then run `bdrive login` on this machine
```
- **`read-only (pull only)`** — you have `read` on the project. The daemon keeps
pulling teammates' changes; your own edits stay journaled locally, never
pushed and never dropped. They go out if you are granted `write` again.
- **`read-only (pull only)`** — the hub refused this device's push. Usually you
have `read` on the project: the daemon keeps pulling teammates' changes, your
own edits stay journaled locally, never pushed and never dropped, and they go
out if you are granted `write` again.
- **`no access to this project — sync paused`** — your access was revoked.
Nothing is pulled, pushed, or written; the working folder is left exactly as
it is. Re-granting resumes on the next tick with no manual step.
Always read the `reason:` line under them — it is the hub's own sentence, and
not every refusal is about project permissions. The common non-permission one
is `this device is not registered to your account on this hub`: your device
identity was never bound to your account, which is fixed by updating `bdrive`
and running `bdrive login` on that machine, not in Project settings. Checking
your permissions there will show `write` and tell you nothing.
`bdrive sync` shows the same two as `remote: read-only (pull only)` /
`remote: no access — sync paused`, and the daemon logs each once on
transition rather than on every tick. Both are permission answers: they are
fixed in the hub's Project settings → People, not on the device. See
`remote: no access — sync paused` with the reason on the line below, and the
daemon logs each once on transition rather than on every tick — including the
cheap local-only ticks between remote passes, which never ask the hub anything
and so never revise its last answer. For the permission answers, the fix is in
the hub's Project settings → People; see
[Project permissions](/concepts/permissions/).
### `bdrive login` and switching hubs