Skip to content

fix(pcc-node): never report a kernel registration the gateway did not confirm - #396

Draft
LamaSu wants to merge 3 commits into
masterfrom
fix/pcc-node-registration-truth
Draft

LamaSu wants to merge 3 commits into
masterfrom
fix/pcc-node-registration-truth

Conversation

@LamaSu

@LamaSu LamaSu commented Sep 24, 2026

Copy link
Copy Markdown
Owner

What

pcc-node could report a kernel as registered when the gateway had rejected the registration. This PR stops that.

Found in incident #2984, where pcc-node start ran against prod with no valid key:

  • POST /api/kernels returned HTTP 401;
  • start still printed Node running. Accepting jobs.;
  • the daemon logged Kernel … registered;
  • the node then polled, announced and heartbeated with the rejected key every few seconds for five minutes. Every request got a 401.

Root cause: register_kernel returned an error dict on failure, and no caller could tell it apart from a registration record. start ignored the value. The daemon logged success whenever no exception was raised. discover --register counted every attempt as registered.

Change

  • register_kernel raises KernelRegistrationError(kernel_id, status, data) for anything other than 200 or 201. That includes status 0 (unreachable) and 202. .auth_rejected is true for 401 and 403.
  • start, on failure:
    • prints Kernel X was NOT registered: <why> Nothing was started. and exits non-zero;
    • skips devices, the signing key, announce and the daemon. The existing comment already required that ("register them only after the authenticated kernel registration succeeds"), but the code did not do it;
    • saves a key the gateway minted in this run before aborting, so the key is not lost. If the save fails, it says so, and it never prints the key.
  • daemon:
    • registered is logged only on success;
    • a rejected key (401/403) stops the daemon before it polls, removes the PID file, and makes start exit 1;
    • other failures are logged as NOT registered (HTTP n), and the Daemon running line says (NOT registered).
  • discover --register: a refused registration counts as failed, and the command exits 1. Output: registered 0, failed 1.

Unchanged: the success path. The same statuses (200/201) count as success, and the same requests are sent in the same order.

Tests

Not in scope

  • Device registration results are still only logged. register_devices has the same return-dict shape, so it is a follow-up.
  • The daemon does not retry a registration that failed for a transient reason. It says so honestly, then carries on as before.

🤖 Generated with Claude Code

https://claude.ai/code/session_01PkeiQMnVePeBGU9svhREFy

LamaSu and others added 3 commits September 24, 2026 15:26
… confirm

Found in incident #2984: after POST /api/kernels returned HTTP 401,
pcc-node start printed "Node running. Accepting jobs." and the daemon
logged "Kernel ... registered", then polled, announced and heartbeated
with the rejected key every few seconds for five minutes.

register_kernel returned an error dict on failure, which no caller could
tell apart from a registration record. It now raises
KernelRegistrationError (status, body, auth_rejected) for anything but
200/201, including status 0 (unreachable).

- start: on failure, say "NOT registered", why, and "Nothing was
  started", and exit non-zero. Devices, signing key, announce and the
  daemon are all skipped (the existing comment already required this).
  A key the gateway minted in this run is saved first, so it is not lost.
- daemon: "registered" is logged only on success. A rejected key (401/403)
  stops the daemon before it polls; start then exits 1. Other failures
  are logged as NOT registered, and the running line says so.
- discover --register: a refused registration counts as failed, not
  registered, and the command exits 1.

Tests: pcc-node 360 passed, 2 failed. The 2 are the headless-consent
failures that also fail on master; #388 fixes them. 24 new tests. The old
TestRegisterKernel.test_failure expected an error dict; it now expects
the exception (reason in its docstring). 8 mutants (reverting each part
of the fix) are all killed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PkeiQMnVePeBGU9svhREFy
…mand

#388 also appends a test class at the end of test_cli.py. Keeping this
class above TestStartCommand lets the two PRs merge without a conflict.
No test changed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PkeiQMnVePeBGU9svhREFy
…file

test_cli.py is back to master's bytes, so this PR and #388 (which appends
to test_cli.py) merge without a conflict. The tests are unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PkeiQMnVePeBGU9svhREFy
LamaSu added a commit that referenced this pull request Sep 29, 2026
…le's gateway is honoured (N57)

Product-steward's review of #457 (bus #4076) adds the confirmation half of N57
and a label fix.

- The gateway is now resolved once: --pcc-base, then PCC_BASE, then the
  config file's pcc_base, then the public default. The banner and
  registration both use it. Before this, start ALWAYS overwrote the config's
  gateway with the CLI value (config.pcc_base = pcc_base), so a gateway chosen
  in `pcc-node setup` was silently replaced by the public default. The banner
  now labels that source "(from <config path>)".
- --yes (-y). When the public gateway comes from the default, and there is no
  --yes and no earlier yes on this machine (~/.pcc-node/confirmed-gateways),
  start asks on a terminal: "Register this machine on the public PCC network
  (https://capability.network)? [y/N]". On yes it remembers. Without a
  terminal it exits 1 with "Pass --yes or set PCC_BASE to register with the
  public network." The check runs after the already-running check and before
  any network step. install.sh exports PCC_BASE (install.sh:187), so it keeps
  working.
- #396's truth half stays required. This commit stays clear of its lines.

Tests: test_start_banner.py now has 8 cases: non-TTY default refuses; --yes;
PCC_BASE; --pcc-base; a config-file gateway that is used, labelled and not
clobbered; a yes on a terminal that is remembered from another directory; no
refuses; the banner comes first. test_cli's start flow and test_discovery's
--discover start pass --yes. Mutation-checked (confirmation bypass, config
clobber, no memory). Full pcc-node suite with HOME in scratch: 346 passed.

agent: pcc-adk (4f6668ed)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant