ruvnet/RuView · error · TypeError
guidance repoRoot must be a string or null
Error message
guidance repoRoot must be a string or null
What it means
refresh_token (auth.py:363) raises this when verify_token succeeds but get_user(sub) returns None or the user record has is_active falsy. The refresh flow re-validates the user on every refresh, so a valid-but-orphaned or deactivated token cannot be exchanged for a new one.
Source
Thrown at harness/homecore/src/guidance.js:69
export function getGuidance(input = {}, options = {}) {
if (!input || typeof input !== 'object' || Array.isArray(input)) {
throw new TypeError('guidance input must be an object');
}
if (!options || typeof options !== 'object' || Array.isArray(options)) {
throw new TypeError('guidance options must be an object');
}
if (input.topic !== undefined && typeof input.topic !== 'string') {
throw new TypeError('guidance topic must be a string');
}
if (input.query !== undefined && typeof input.query !== 'string') {
throw new TypeError('guidance query must be a string');
}
if (input.limit !== undefined && (typeof input.limit !== 'number' || !Number.isFinite(input.limit))) {
throw new TypeError('guidance limit must be a finite number');
}
if (options.repoRoot !== undefined && options.repoRoot !== null && typeof options.repoRoot !== 'string') {
throw new TypeError('guidance repoRoot must be a string or null');
}
const topic = input.topic === undefined ? 'overview' : input.topic;
if (!GUIDANCE_TOPICS.includes(topic)) {
throw new RangeError(`unsupported guidance topic: ${topic}`);
}
const query = input.query === undefined ? '' : input.query.trim();
if (query && (query.length < 2 || query.length > 500)) {
throw new RangeError('guidance query must contain 2..500 characters');
}
const rawLimit = input.limit === undefined ? 20 : input.limit;
if (rawLimit < 1 || rawLimit > 20) {
throw new RangeError('guidance limit must be between 1 and 20');
}
const limit = Math.floor(rawLimit);
const wanted = tokenize(query);
const candidates = CAPABILITIESView on GitHub (pinned to 4685618388)
Solutions
- Fall back to full login (username/password) to obtain a brand-new token
- Ensure the user exists and is_active=True in this process before refreshing
- Persist the user store so restarts do not orphan live tokens
Example fix
# before
tokens = await auth.refresh_token(old_token)
# after
try:
tokens = await auth.refresh_token(old_token)
except AuthenticationError:
tokens = await auth.login(username, password) # full re-auth Defensive patterns
Strategy: fallback
Validate before calling
def refresh_will_succeed(token_manager, user_manager, token: str) -> bool:
"""refresh_token re-checks user existence and is_active."""
claims = token_manager.decode_token_claims(token)
if claims is None:
return False
user = user_manager.get_user(claims.get("sub", ""))
return user is not None and bool(user.get("is_active", False)) Try / catch
try:
tokens = await middleware.refresh_token(token)
except AuthenticationError:
# covers 'User not found or inactive' and the masked generic case
tokens = await middleware.login(username, password) # fallback re-auth Prevention
- Chain refresh -> login fallback in every client
- Refresh before expiry to avoid the inactive-user edge at the wire
- Keep the user store persistent so refresh survives restarts
When it happens
Trigger: Calling refresh_token with a token whose user was created before a server restart (in-memory store now empty); user deactivated between issuance and refresh; sub claim is a username never registered on this instance.
Common situations: Long-running clients attempting silent refresh after a backend restart; deployments where refresh is expected to survive user-store resets; multi-instance setups with per-process user dicts.
Related errors
- duplicate brain id: ${record.id}
- unsupported guidance topic: ${topic}
- brain line ${index + 1}: exceeds 16 KiB
- brain line ${index + 1}: ${error.message}
- brain line ${index + 1}: ${errors.join('; ')}
AI-assisted analysis of ruvnet/RuView@4685618388 (2026-08-16).
Data as JSON: /api/errors/8e828d204bd1e78f.
Report an issue: GitHub.