Skip to content

Usage sign-in in the account's folder; unknown usage shares the pool (0.16.0) - #84

Open
JOsacky wants to merge 2 commits into
mainfrom
claude/usage-login-own-dir
Open

JOsacky wants to merge 2 commits into
mainfrom
claude/usage-login-own-dir

Conversation

@JOsacky

@JOsacky JOsacky commented Nov 10, 2026

Copy link
Copy Markdown
Member

Why

On the mac mini every Claude job went to claude-glossa (213 jobs in 24 h vs 3 each for claude-carimage and claude-skillhook). Two causes:

  1. A setup token cannot read usage. Probed on the mini with only the job token: Claude Code answers "signed in, subscription", but no usage windows, email or organization. The first commit here (until now only installed by hand on the mini) adds usage_login, a regular sign-in used only to read usage. Only glossa's worked (it points at ~/.claude); the others' live in separate ~/.skillhook/usage-logins/<name> folders that are signed out.
  2. Unknown usage ranked behind every measured account. That commit's orderPool put an account whose usage is unknown (never read, or the reading expired) in a tier behind any account with measured headroom. So glossa, the only measured account, got every job unless it was over 90 % (exactly 11:45–12:20 UTC on Nov 10, when its 5-hour window was full, were the only jobs that went elsewhere).

What changes

  • usage_login.dir is optional and defaults to the account's own dir: one folder per account holds both the job token (used by runs) and the usage sign-in (used only by usage reads; Claude Code prefers CLAUDE_CODE_OAUTH_TOKEN over the stored sign-in).
  • skillhook accounts login <name> --usage [--sso] runs claude auth login --email <usage_login.email> in that folder without the token or inherited credentials, then reads the usage. The "Sign in to Claude Code in …" problem names this command.
  • orderPool: back to three tiers (usage left, near-full, limited). Unknown usage is in the first tier and counts as the fullest band among the measured accounts there, so it takes turns with them, never jumps ahead of an account measured to have more left, and never waits behind one measured to have less. One band per account keeps the comparator transitive.
  • Docs (accounts.md, operations.md), schema, changelog, version 0.16.0.

Tests

  • New: the mini's situation (one measured at 62 %, one expired, one never read, one at 92 %) round-robins over the first three; unknown vs low/high measured; in-flight still first.
  • New: usage_login dir defaults; accounts login --usage errors and the happy path with the fake CLI.
  • pnpm run check: 525 tests pass, schema and release metadata current.

Measure

After this ships to the mini and the three usage sign-ins are signed in: skillhook accounts list shows jobs (24 h) spread across claude-carimage, claude-skillhook and claude-glossa instead of ~98 % on glossa, and the cloud Account pool shows a recent "Read" time for each.

🤖 Generated with Claude Code

JOsacky and others added 2 commits November 11, 2026 15:18
…re the pool (0.16.0)

A setup token runs jobs but cannot read usage, so a token account reads it through
usage_login, a regular sign-in. Its dir now defaults to the account's own directory, and
`skillhook accounts login <name> --usage` signs it in there, so one folder per account holds
both credentials.

orderPool put accounts with unknown usage behind every measured one, so a pool where only one
account's usage could be read sent it every job until it neared a limit. Unknown usage now
counts as the fullest band of the accounts with usage left: it takes turns with them, never
goes ahead of one measured to have more left, nor behind one measured to have less.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@JOsacky
JOsacky enabled auto-merge (squash) November 11, 2026 20:20
@JOsacky
JOsacky force-pushed the claude/usage-login-own-dir branch from 3ac2ca7 to 96b5556 Compare November 11, 2026 20:20
JOsacky added a commit that referenced this pull request Nov 12, 2026
**Version: lands after #84, which takes 0.16.0. This branch is rebased
and re-bumped to 0.17.0 once #84 is on main.**

## Why

Repositories should declare their timers in `skillhook.yaml` and have
that be the source of truth, but a person still needs to switch one off
quickly (from Skillhook Cloud) when it misbehaves, without a commit.
Today the only off switch is editing the file, so repositories moved
their timers into Cloud-owned schedules, which then drift from what the
repo declares.

## What

- `Scheduler.pause(name, {by, reason, until})` / `resume(name)`. The
pause is stored in `jobs/.schedules.json` (not the skill's file), so
repo pulls, hook edits and restarts keep it.
- While paused nothing fires. The webhook and manual runs still work.
Slots that pass count as `skipped` but are never caught up, and they
emit no per-slot events, so a week of a 10-minute timer doesn't produce
a thousand. On resume, or when `until` passes, it fires from the next
slot.
- Surfaces: `skillhook schedules pause <name> [--reason] [--until
2h|ISO]` / `resume` (through the running server, or the state file when
none runs), `POST /schedules/<name>/pause|resume` (admin), and cloud
commands `schedule.pause {name, reason?, until?}` / `schedule.resume
{name}` (control class; the next sync carries a fresh snapshot).
- `ScheduleStatus.paused` (`{at, by?, reason?, until?}` or `null`) in
`schedules list`, `GET /health` and the cloud snapshot.

## Compatibility

Older machines refuse a whole sync response that carries an unknown
command type, so the cloud must gate `schedule.pause` /
`schedule.resume` with `COMMAND_SINCE` = `0.17.0`. That's in the
follow-up skillhook-cloud PR, after this publishes.

## Checked

`pnpm run check`: typecheck, 528 tests, build, schema, release metadata.
New tests cover the scheduler (quiet skip, no catch-up on resume,
`until` expiry across a restart, refusals, offline state), the admin
route, the CLI and a cloud round trip over `FakeCloud` (pause → snapshot
shows it → resume).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
JOsacky added a commit that referenced this pull request Nov 13, 2026
…nfig (#87)

## Why
On 2026-11-12 at 12:20Z, the main operator daemon (0.15.2) restarted
onto the newly installed 0.16.0. 0.16.0 rejects
`accounts.*.usage_login`, which was written for the unmerged #84.
launchd restarted it **128 times** (`Invalid
~/.skillhook/skillhook.json: Unrecognized key "usage_login"`), and for
**17 min 20 s** nothing ran: no jobs, no schedules, no hosted ingress. A
person-equivalent fix (removing the keys) brought it back. Tracked as
RA-019 in the operator's reliability backlog.

## What
- `installedAcceptsConfig(paths)` in `src/update.ts` runs `node
<installed dist/cli.js> config show --json` with `SKILLHOOK_HOME` set to
the server's home. It returns one of three results:
  - `ok: true`: the new version loaded the config.
  - `ok: false` with the error: the new version exited non-zero.
- `ok: undefined`: the check could not run (spawn error or a 30 s
timeout).
- The restart-on-newer-version interval in `serve.ts` runs the check
before calling `control.restart()`:
- **Rejected:** the server logs an `error` once per (version, error),
keeps the current version running and checks again every minute. Fixing
`skillhook.json` or shipping a later release lets the restart go ahead.
- **Unknown:** the server logs a `warn` and restarts as before. A
missing node binary (see #71) or a hung check under heavy load must not
pin an old version forever.
- CHANGELOG has an entry under Unreleased.

## Tests
- `src/update.test.ts` has unit cases for each outcome: accepted,
rejected with message, bare exit code, missing executable, and timeout.
They also check that the child gets this home and not the parent's
`SKILLHOOK_HOME`.
- An integration case runs the real CLI (`tsx src/cli.ts`) against a
valid config and against one with an unknown key.
- Local results: `pnpm test` passed 533/533, `typecheck` passed, `build`
passed, `release --check` passed.

Reversible: the change only skips a restart. It writes no data, and the
protocol is unchanged.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: GitHub Actions Bot <github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <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