use super::refresh::{self, Place, Progress, Refreshing, Renewed, Stop};
use super::{Settled, identify, live_store, purge, read_stored, shape, write_lock};
use crate::context::Context;
use crate::error::{Cause, Error, Partway, Result};
use crate::provider::Owner;
use crate::provider::claude::live;
use crate::provider::{self, ProviderError, ProviderId};
use crate::service::{Permit, Warning};
use crate::state::{self, Account, State};
use crate::store::{self, RawStore};
use crate::{fault, lock, park};
use serde_json::Value;
use std::path::{Path, PathBuf};
const TOOL: ProviderId = ProviderId::Claude;
#[derive(Debug, Clone, PartialEq)]
pub struct Left {
pub path: PathBuf,
pub seen: String,
pub login: Foreseen,
pub dropped: Vec<String>,
}
impl Left {
pub fn refusal(&self) -> Option<Error> {
match &self.login {
Foreseen::NotEnrolled(owner) => Some(not_enrolled(&self.path, owner)),
Foreseen::Kept(_) | Foreseen::Untold => None,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Foreseen {
Kept(Kept),
NotEnrolled(Owner),
Untold,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum Kept {
NoLogin,
Refused,
Stored { label: Option<String> },
AlreadyParked { label: String },
SecondSignIn { label: String },
ParkedNow { label: String },
ParkKept { label: String },
}
impl Kept {
pub fn code(&self) -> &'static str {
match self {
Kept::NoLogin => "no_login",
Kept::Refused => "login_refused",
Kept::Stored { .. } => "stored",
Kept::AlreadyParked { .. } => "already_parked",
Kept::SecondSignIn { .. } => "second_sign_in",
Kept::ParkedNow { .. } => "parked",
Kept::ParkKept { .. } => "park_kept",
}
}
pub fn account(&self) -> Option<&str> {
match self {
Kept::NoLogin | Kept::Refused => None,
Kept::Stored { label } => label.as_deref(),
Kept::AlreadyParked { label }
| Kept::SecondSignIn { label }
| Kept::ParkedNow { label }
| Kept::ParkKept { label } => Some(label),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Stowed {
pub path: PathBuf,
pub kept: Kept,
pub dropped: Vec<String>,
}
pub(crate) fn find(ctx: &Context, state: &State) -> Result<Option<Left>> {
let tool = provider::claude_code();
let path = live::credential_file(ctx);
let live = live_store(ctx, tool)?;
let Some((_, raw)) = read_left(&live)? else {
return Ok(None);
};
let held = contents(&raw)?;
let stored = read_stored(tool, &live)?.map(|(_, document)| document);
let login = match judge(ctx, state, &held, stored.as_ref(), &path)? {
Whose::Kept(kept) => Foreseen::Kept(kept),
Whose::Untold => Foreseen::Untold,
Whose::Owner(owner) => match state.account_of(TOOL, &owner) {
None => Foreseen::NotEnrolled(owner),
Some(account) => {
let told = stored
.map(|document| identify::told(ctx, state, tool, &document))
.transpose()
.map_err(|e| super::unidentified(TOOL, e))?;
let stored = match told {
None => None,
Some(Some(found)) => Some(found.owner),
Some(None) => state.in_use(TOOL).and_then(|known| known.owner.clone()),
};
Foreseen::Kept(kept_for(ctx, state, account, stored.as_ref())?)
}
},
};
Ok(Some(Left {
path,
seen: store::fingerprint(&raw),
login,
dropped: held.dropped,
}))
}
pub(crate) fn stow(settled: Settled, seen: &str) -> Result<(Stowed, Vec<Warning>)> {
let Settled {
_exclusive,
mut state,
ctx,
permit,
} = settled;
let mut stowing = Stowing {
ctx: &ctx,
permit,
path: live::credential_file(&ctx),
partway: Partway::Nothing,
warnings: Vec::new(),
lock_lost: false,
};
let done = stowing.run(&mut state, seen);
let Stowing {
partway, warnings, ..
} = stowing;
match done {
Ok(stowed) => {
purge(&ctx, permit, &mut state);
Ok((stowed, warnings))
}
Err(lost @ Error::RenewalLost { .. }) => Err(lost),
Err(error) => Err(Error::StowStopped {
partway,
warnings,
error: Box::new(error),
}),
}
}
struct Stowing<'a> {
ctx: &'a Context,
permit: Permit,
path: PathBuf,
partway: Partway,
warnings: Vec<Warning>,
lock_lost: bool,
}
impl Stowing<'_> {
fn run(&mut self, state: &mut State, seen: &str) -> Result<Stowed> {
let (ctx, permit) = (self.ctx, self.permit);
let tool = provider::claude_code();
refresh::finished(ctx, permit, state, TOOL)?;
let live = live_store(ctx, tool)?;
let (_, mut raw) = read_left(&live)?
.filter(|(_, raw)| store::fingerprint(raw) == seen)
.ok_or_else(|| self.changed())?;
let mut held = contents(&raw)?;
let stored = read_stored(tool, &live)?.map(|(_, document)| document);
let judged = judge(ctx, state, &held, stored.as_ref(), &self.path)?;
if let Whose::Owner(owner) = &judged
&& state.account_of(TOOL, owner).is_none()
{
return Err(not_enrolled(&self.path, owner));
}
let (stored, stored_owner) = match judged {
Whose::Kept(_) => (stored, None),
Whose::Owner(_) | Whose::Untold => match self.told(state)? {
identify::Live::Login(login) => {
self.lock_lost |= login.lock_lost;
(Some(login.document), Some(login.owner))
}
identify::Live::Nothing => (None, None),
},
};
fault::point("stow.identified");
let refreshing = Refreshing::take(ctx, permit, tool)?;
if unchanged(&live, &raw)?.is_none() {
return Err(self.changed());
}
let kept = match judged {
Whose::Kept(kept) => kept,
Whose::Owner(owner) => self.keep(state, &owner, stored_owner.as_ref(), &held)?,
Whose::Untold => match self.renew(state, &live, &mut raw, &mut held)? {
None => Kept::Refused,
Some(owner) => self.keep(state, &owner, stored_owner.as_ref(), &held)?,
},
};
fault::point("stow.kept");
let writing = write_lock(ctx, permit, tool)?;
let file = unchanged(&live, &raw)?.ok_or_else(|| self.changed())?;
if matches!(kept, Kept::Stored { .. } | Kept::SecondSignIn { .. })
&& !still_stored(&live, stored.as_ref())?
{
return Err(Error::StoredLoginChanged {
path: self.path.clone(),
});
}
file.delete(permit, &live.service)?;
if state.file_gone() {
let _ = state::save(ctx, permit, state);
}
self.lock_lost |= let_go(writing) | Refreshing::let_go(refreshing);
if self.lock_lost {
self.warnings.push(Warning::LockCompromised { tool: TOOL });
}
Ok(Stowed {
path: self.path.clone(),
kept,
dropped: held.dropped,
})
}
fn told(&mut self, state: &mut State) -> Result<identify::Live> {
let told = identify::now(self.ctx, self.permit, state, provider::claude_code());
if told.is_err() && state.renewing.iter().any(|left| left.tool == TOOL) {
self.partway = Partway::StoredRenewedKept;
}
told
}
fn renew(
&mut self,
state: &mut State,
live: &provider::LiveStore,
raw: &mut String,
held: &mut Contents,
) -> Result<Option<Owner>> {
let tool = provider::claude_code();
let slice = held.slice.clone().expect("only a login is renewed");
let file = self.ctx.host().file(self.path.clone());
let place = Place {
store: file.as_ref(),
service: &live.service,
};
let mut progress = Progress::default();
let renewed = refresh::renew(
self.ctx,
self.permit,
state,
tool,
place,
raw,
&slice,
&mut progress,
);
self.lock_lost |= progress.lock_lost;
if progress.saved {
self.partway = Partway::Renewed;
} else if progress.kept {
self.partway = Partway::RenewedKept;
} else if progress.spent {
self.partway = Partway::RenewedUnwritten;
}
let renewed = match renewed {
Ok(Renewed::Saved(renewed)) => renewed,
Ok(Renewed::Refused) => return Ok(None),
Err(Stop::Changed) => return Err(self.changed()),
Err(Stop::Unrenewed(error)) => return Err(not_identified(&self.path, &error)),
Err(Stop::Failed(error)) => return Err(error),
};
*held = contents(&renewed)?;
*raw = renewed;
let login = held.slice.clone().expect("a renewed login");
tool.identify(self.ctx, &provider::Credential::new(TOOL, login))
.map(Some)
.map_err(|error| not_identified(&self.path, &error))
}
fn keep(
&mut self,
state: &mut State,
owner: &Owner,
stored: Option<&Owner>,
held: &Contents,
) -> Result<Kept> {
let account = state
.account_of(TOOL, owner)
.ok_or_else(|| not_enrolled(&self.path, owner))?;
let kept = kept_for(self.ctx, state, account, stored)?;
if let (Kept::ParkedNow { label }, Some(slice)) = (&kept, &held.slice) {
let parking = park_for(self.ctx, self.permit, state, owner, slice)?;
self.warnings.extend(parking);
self.partway = Partway::Parked {
label: label.clone(),
};
}
Ok(kept)
}
fn changed(&self) -> Error {
Error::LeftLoginChanged {
path: self.path.clone(),
}
}
}
fn let_go(writing: Option<lock::Guard>) -> bool {
writing.as_ref().is_some_and(lock::Guard::compromised)
}
fn read_left(live: &provider::LiveStore) -> Result<Option<(&dyn RawStore, String)>> {
let Some(file) = store::behind(&live.chain, &live.service)? else {
return Ok(None);
};
Ok(file.read(&live.service)?.map(|raw| (file, raw)))
}
fn unchanged<'a>(live: &'a provider::LiveStore, raw: &str) -> Result<Option<&'a dyn RawStore>> {
Ok(read_left(live)?
.filter(|(_, now)| now == raw)
.map(|(file, _)| file))
}
fn still_stored(live: &provider::LiveStore, judged: Option<&Value>) -> Result<bool> {
let tool = provider::claude_code();
let login = |document: &Value| tool.slice(document).ok();
let now = read_stored(tool, live)?.map(|(_, document)| document);
Ok(now.as_ref().and_then(login) == judged.and_then(login))
}
struct Contents {
slice: Option<Value>,
dropped: Vec<String>,
}
fn contents(raw: &str) -> Result<Contents> {
let document = live::document_in(raw);
let slice = live::holds_a_login(&document)
.then(|| provider::claude_code().slice(&document))
.transpose()
.map_err(|e| shape(TOOL, e))?;
let login = slice.as_ref().and_then(Value::as_object);
let dropped = document
.as_object()
.into_iter()
.flat_map(serde_json::Map::keys)
.filter(|key| !login.is_some_and(|login| login.contains_key(*key)))
.cloned()
.collect();
Ok(Contents { slice, dropped })
}
enum Whose {
Kept(Kept),
Owner(Owner),
Untold,
}
fn judge(
ctx: &Context,
state: &State,
held: &Contents,
stored: Option<&Value>,
path: &Path,
) -> Result<Whose> {
let Some(slice) = &held.slice else {
return Ok(Whose::Kept(Kept::NoLogin));
};
let tool = provider::claude_code();
let login = tool.fingerprint(slice);
if !login.is_empty() {
if stored.is_some_and(|stored| tool.fingerprint(stored) == login) {
let label = state
.in_use(TOOL)
.filter(|record| record.login == login)
.and_then(|_| state.account_in_use(TOOL))
.map(|account| state.typed(&account.key()));
return Ok(Whose::Kept(Kept::Stored { label }));
}
if let Some((account, parked)) = state.accounts.iter().find_map(|account| {
account
.parked
.as_ref()
.filter(|park| account.provider() == TOOL && park.login == login)
.map(|park| (account, park))
}) {
return Ok(if reads_back(ctx, account, parked)? {
Whose::Kept(Kept::AlreadyParked {
label: state.typed(&account.key()),
})
} else {
Whose::Owner(account.owner())
});
}
if let Some(owner) = state
.in_use(TOOL)
.filter(|record| record.login == login)
.and_then(|record| record.owner.clone())
{
return Ok(Whose::Owner(owner));
}
}
let untold = || {
if login.is_empty() {
Whose::Kept(Kept::Refused)
} else {
Whose::Untold
}
};
if tool
.expiry(slice)
.access_expires_at
.is_some_and(|at| at <= ctx.now())
{
return Ok(untold());
}
match tool.identify(ctx, &provider::Credential::new(TOOL, slice.clone())) {
Ok(found) => Ok(Whose::Owner(found)),
Err(ProviderError::Unauthorized | ProviderError::ShapeUnexpected { .. }) => Ok(untold()),
Err(error) => Err(not_identified(path, &error)),
}
}
fn kept_for(
ctx: &Context,
state: &State,
account: &Account,
stored: Option<&Owner>,
) -> Result<Kept> {
let label = state.typed(&account.key());
Ok(if stored.is_some_and(|stored| account.owned_by(stored)) {
Kept::SecondSignIn { label }
} else if switchable(ctx, state, account)? {
Kept::ParkKept { label }
} else {
Kept::ParkedNow { label }
})
}
fn switchable(ctx: &Context, state: &State, account: &Account) -> Result<bool> {
match account
.parked
.as_ref()
.filter(|parked| parked.restorable_at(ctx.now()) && !state.is_from_file(&parked.service))
{
Some(parked) => reads_back(ctx, account, parked),
None => Ok(false),
}
}
fn reads_back(ctx: &Context, account: &Account, parked: &state::Park) -> Result<bool> {
match park::load(ctx, provider::claude_code(), &account.key(), parked) {
Ok(_) => Ok(true),
Err(Error::Store(unreadable)) => Err(Error::Store(unreadable)),
Err(_) => Ok(false),
}
}
fn park_for(
ctx: &Context,
permit: Permit,
state: &mut State,
owner: &Owner,
slice: &Value,
) -> Result<Option<Warning>> {
let account = state
.account_of(TOOL, owner)
.expect("parked only for an enrolled account");
let (key, id, label) = (
account.key(),
account.id.clone(),
state.typed(&account.key()),
);
let parking = park::price(
ctx,
TOOL,
&label,
&park::service_name(&id, ctx.now_millis()),
slice,
)?;
let service = park::reserve(ctx, permit, &id)?;
state.copying_from_file(&service);
state::save(ctx, permit, state)?;
let parked = park::store_at(ctx, permit, provider::claude_code(), &service, slice)?;
fault::point("stow.park_stored");
state.park(&key, parked);
state::save(ctx, permit, state).inspect_err(|_| {
let _ = store::vault_delete(ctx, permit, &service);
})?;
Ok(parking)
}
fn not_enrolled(path: &Path, owner: &Owner) -> Error {
Error::LeftLoginNotEnrolled {
path: path.to_path_buf(),
email: owner.email.clone(),
organization: owner.organization_uuid.clone(),
}
}
fn not_identified(path: &Path, error: &ProviderError) -> Error {
Error::LeftLoginUnidentified {
path: path.to_path_buf(),
cause: Cause::of_provider(error),
detail: error.to_string(),
}
}
#[cfg(test)]
mod tests {
use super::super::harness::{
Machine, NOW, Session, account, audit_lines, document, lapsed, lets_go_of_the_write_lock,
lock_dir, machine, oauth, owner, renews, renews_meanwhile, saves, state_file,
takes_the_write_lock, write_target,
};
use super::super::settle;
use super::*;
use crate::api::scripted::{Asked, Trouble};
use crate::service::Pitboard;
use crate::store::memory::Fault;
use serde_json::json;
fn leave(m: &Machine, contents: &Value) {
left_file(m).plant(&m.service, &contents.to_string());
}
fn left_file(m: &Machine) -> std::sync::Arc<store::memory::MemoryStore> {
m.mem.file_at(live::credential_file(&m.ctx))
}
fn in_the_file(m: &Machine) -> Option<String> {
let held = left_file(m).peek(&m.service)?;
let document: Value = serde_json::from_str(&held).expect("JSON");
Some(provider::document(TOOL).fingerprint(&document))
}
fn enrolled(m: &Machine, label: &str) {
let mut state = state::load(&m.ctx).expect("state");
state.accounts.push(account(label, label, None));
state::save(&m.ctx, Permit::for_a_test(), &state).expect("saved");
}
fn found(m: &Machine) -> Left {
let state = state::load(&m.ctx).expect("state");
find(&m.ctx, &state)
.expect("a look")
.expect("something left")
}
fn stow_now(m: &Machine) -> Result<(Stowed, Vec<Warning>)> {
let seen = found(m).seen;
stow_seen(m, &seen)
}
fn stow_seen(m: &Machine, seen: &str) -> Result<(Stowed, Vec<Warning>)> {
let settled = settle(&m.ctx, Permit::for_a_test(), Some(TOOL))?.0;
stow(settled, seen)
}
fn stow_while(
m: &Machine,
point: &'static str,
elsewhere: impl FnOnce() + 'static,
) -> Result<(Stowed, Vec<Warning>)> {
let seen = found(m).seen;
fault::meanwhile(point, elsewhere, || stow_seen(m, &seen))
}
fn rewrites_the_file(m: &Machine, contents: &Value) -> impl FnOnce() + 'static {
let (file, service, contents) = (left_file(m), m.service.clone(), contents.to_string());
move || file.plant(&service, &contents)
}
fn locks_the_keychain(m: &Machine) -> impl FnOnce() + 'static {
let (keychain, service) = m.live_store();
move || keychain.fault(&service, Fault::Locked)
}
fn signs_in(m: &Machine, document: &Value) -> impl FnOnce() + 'static {
let (keychain, service) = (std::sync::Arc::clone(m.mem.live()), m.service.clone());
let document = document.to_string();
move || keychain.plant(&service, &document)
}
fn renewed_later(refresh: &str) -> Value {
let mut login = document(refresh);
login["claudeAiOauth"]["expiresAt"] = json!((NOW + 7200) * 1000);
login
}
fn parked_fingerprint(m: &Machine, label: &str) -> Option<String> {
let state = state::load(&m.ctx).expect("state");
let parked = state.get(&m.key(label))?.parked.clone()?;
Some(parked.login)
}
fn fingerprint(refresh: &str) -> String {
store::fingerprint(refresh)
}
#[test]
#[cfg_attr(
windows,
ignore = "W23: Claude Code's Credential Manager store, which a machine in memory plays"
)]
fn the_login_claude_code_has_stored_goes_with_the_file() {
let m = machine("stow-stored");
leave(&m, &document("here-refresh"));
let (stowed, _) = stow_now(&m).expect("put away");
assert_eq!(
stowed.kept,
Kept::Stored {
label: Some("here".into())
}
);
assert_eq!(stowed.dropped, ["mcpOAuth"]);
assert_eq!(in_the_file(&m), None);
assert_eq!(m.live(), Some(document("here-refresh")));
assert!(m.api.asked().is_empty(), "{:?}", m.api.asked());
}
#[test]
#[cfg_attr(
windows,
ignore = "W23: Claude Code's Credential Manager store, which a machine in memory plays"
)]
fn a_login_pitboard_keeps_parked_goes_with_the_file() {
let m = machine("stow-already-parked");
leave(&m, &json!({"claudeAiOauth": oauth("there-refresh", 30)}));
let vault = m.mem.vault().services();
let (stowed, _) = stow_now(&m).expect("put away");
assert_eq!(
stowed.kept,
Kept::AlreadyParked {
label: "there".into()
}
);
assert_eq!(in_the_file(&m), None);
assert_eq!(m.mem.vault().services(), vault);
assert_eq!(
parked_fingerprint(&m, "there"),
Some(fingerprint("there-refresh"))
);
}
#[test]
#[cfg_attr(
windows,
ignore = "W23: Claude Code's Credential Manager store, which a machine in memory plays"
)]
fn a_login_whose_park_does_not_read_back_is_parked_in_its_place() {
let m = machine("stow-park-gone");
m.mem.vault().delete_everything();
leave(&m, &json!({"claudeAiOauth": oauth("there-refresh", 30)}));
let there = Kept::ParkedNow {
label: "there".into(),
};
assert_eq!(found(&m).login, Foreseen::Kept(there.clone()));
let (stowed, _) = stow_now(&m).expect("put away");
assert_eq!(stowed.kept, there);
assert_eq!(in_the_file(&m), None);
let state = state::load(&m.ctx).expect("state");
let parked = state
.get(&m.key("there"))
.and_then(|a| a.parked.clone())
.expect("parked");
let held = park::load(&m.ctx, provider::document(TOOL), &m.key("there"), &parked)
.expect("it reads back");
assert_eq!(held["claudeAiOauth"]["refreshToken"], "there-refresh");
assert!(m.api.asked().is_empty(), "{:?}", m.api.asked());
}
#[test]
#[cfg_attr(
windows,
ignore = "W23: Claude Code's Credential Manager store, which a machine in memory plays"
)]
fn another_login_of_the_account_in_use_is_dropped_as_a_second_sign_in() {
let m = machine("stow-second-sign-in");
m.api.owned_by("access-here-again", owner("here"));
leave(&m, &document("here-again"));
let (stowed, _) = stow_now(&m).expect("put away");
assert_eq!(
stowed.kept,
Kept::SecondSignIn {
label: "here".into()
}
);
assert_eq!(in_the_file(&m), None);
assert_eq!(m.live(), Some(document("here-refresh")));
assert_eq!(parked_fingerprint(&m, "here"), None);
}
#[test]
#[cfg_attr(
windows,
ignore = "W23: Claude Code's Credential Manager store, which a machine in memory plays"
)]
fn a_login_of_an_account_with_nothing_parked_is_parked_before_the_file_goes() {
let m = machine("stow-parks");
enrolled(&m, "elsewhere");
m.api
.owned_by("access-elsewhere-refresh", owner("elsewhere"));
leave(&m, &document("elsewhere-refresh"));
let (stowed, _) = stow_now(&m).expect("put away");
assert_eq!(
stowed.kept,
Kept::ParkedNow {
label: "elsewhere".into()
}
);
assert_eq!(in_the_file(&m), None);
assert_eq!(
parked_fingerprint(&m, "elsewhere"),
Some(fingerprint("elsewhere-refresh"))
);
let state = state::load(&m.ctx).expect("state");
let parked = state
.get(&m.key("elsewhere"))
.and_then(|a| a.parked.clone())
.expect("parked");
let held = park::load(
&m.ctx,
provider::document(TOOL),
&m.key("elsewhere"),
&parked,
)
.expect("it reads back");
assert_eq!(held["claudeAiOauth"]["refreshToken"], "elsewhere-refresh");
assert!(
held.get("mcpOAuth").is_none(),
"the machine's keys are not the account's"
);
}
#[test]
#[cfg_attr(
windows,
ignore = "W23: Claude Code's Credential Manager store, which a machine in memory plays"
)]
fn a_login_of_an_account_whose_park_has_expired_takes_its_place() {
let m = machine("stow-expired-park");
let mut state = state::load(&m.ctx).expect("state");
let expired = state
.accounts
.iter_mut()
.find(|a| a.label == "there")
.and_then(|a| a.parked.as_mut())
.expect("there is parked");
expired.refresh_expires_at = Some(NOW - 1);
state::save(&m.ctx, Permit::for_a_test(), &state).expect("saved");
m.api.owned_by("access-there-again", owner("there"));
leave(&m, &document("there-again"));
let (stowed, _) = stow_now(&m).expect("put away");
assert_eq!(
stowed.kept,
Kept::ParkedNow {
label: "there".into()
}
);
assert_eq!(
parked_fingerprint(&m, "there"),
Some(fingerprint("there-again"))
);
assert_eq!(in_the_file(&m), None);
}
#[test]
#[cfg_attr(
windows,
ignore = "W23: Claude Code's Credential Manager store, which a machine in memory plays"
)]
fn an_account_with_a_park_it_can_switch_to_keeps_that_park() {
let m = machine("stow-park-kept");
m.api.owned_by("access-there-again", owner("there"));
leave(&m, &document("there-again"));
let (stowed, _) = stow_now(&m).expect("put away");
assert_eq!(
stowed.kept,
Kept::ParkKept {
label: "there".into()
}
);
assert_eq!(
parked_fingerprint(&m, "there"),
Some(fingerprint("there-refresh"))
);
assert_eq!(in_the_file(&m), None);
}
#[test]
#[cfg_attr(
windows,
ignore = "W23: Claude Code's Credential Manager store, which a machine in memory plays"
)]
fn a_park_it_can_switch_to_is_kept_though_the_files_login_is_newer() {
let kept = Kept::ParkKept {
label: "there".into(),
};
let m = machine("stow-park-kept-newer");
m.api.owned_by("access-there-again", owner("there"));
leave(&m, &renewed_later("there-again"));
let vault = m.mem.vault().services();
assert_eq!(found(&m).login, Foreseen::Kept(kept.clone()));
let (stowed, _) = stow_now(&m).expect("put away");
assert_eq!(stowed.kept, kept);
assert_eq!(m.mem.vault().services(), vault);
assert_eq!(
parked_fingerprint(&m, "there"),
Some(fingerprint("there-refresh"))
);
assert_eq!(in_the_file(&m), None);
let m = machine("stow-park-kept-renewed");
m.api.renews(
"there-again",
provider::claude::api::Renewed {
access_token: "access-there-renewed".into(),
refresh_token: Some("there-renewed".into()),
expires_in: 28_800,
refresh_token_expires_in: Some(30 * 86_400),
scopes: None,
at: None,
},
);
m.api.owned_by("access-there-renewed", owner("there"));
leave(&m, &lapsed("there-again"));
let vault = m.mem.vault().services();
let (stowed, _) = stow_now(&m).expect("put away");
assert_eq!(stowed.kept, kept);
assert_eq!(m.mem.vault().services(), vault);
assert_eq!(
parked_fingerprint(&m, "there"),
Some(fingerprint("there-refresh"))
);
assert_eq!(in_the_file(&m), None);
}
#[test]
#[cfg_attr(
windows,
ignore = "W23: Claude Code's Credential Manager store, which a machine in memory plays"
)]
fn a_login_parked_from_the_file_is_kept_once_that_file_has_gone() {
let m = machine("stow-parked-then-kept");
enrolled(&m, "elsewhere");
m.api
.owned_by("access-elsewhere-refresh", owner("elsewhere"));
m.api.owned_by("access-elsewhere-again", owner("elsewhere"));
leave(&m, &document("elsewhere-refresh"));
let (stowed, _) = stow_now(&m).expect("put away");
assert_eq!(
stowed.kept,
Kept::ParkedNow {
label: "elsewhere".into()
}
);
assert!(state::load(&m.ctx).expect("state").from_file.is_empty());
leave(&m, &renewed_later("elsewhere-again"));
let (stowed, _) = stow_now(&m).expect("put away");
assert_eq!(
stowed.kept,
Kept::ParkKept {
label: "elsewhere".into()
}
);
assert_eq!(
parked_fingerprint(&m, "elsewhere"),
Some(fingerprint("elsewhere-refresh"))
);
}
#[test]
#[cfg_attr(
windows,
ignore = "W23: Claude Code's Credential Manager store, which a machine in memory plays"
)]
fn a_copy_a_killed_run_never_recorded_is_not_kept_over_the_login_renewed_since() {
let m = machine("stow-killed-unrecorded");
enrolled(&m, "elsewhere");
m.api
.owned_by("access-elsewhere-refresh", owner("elsewhere"));
leave(&m, &document("elsewhere-refresh"));
let seen = found(&m).seen;
let died = fault::killing("stow.park_stored", || stow_seen(&m, &seen));
assert_eq!(died.unwrap_err(), "stow.park_stored");
assert_eq!(parked_fingerprint(&m, "elsewhere"), None);
m.api
.renew_trouble("elsewhere-refresh", Trouble::InvalidGrant);
m.api
.owned_by("access-elsewhere-renewed", owner("elsewhere"));
leave(&m, &renewed_later("elsewhere-renewed"));
let (stowed, _) = stow_now(&m).expect("put away again");
assert_eq!(
stowed.kept,
Kept::ParkedNow {
label: "elsewhere".into()
}
);
assert_eq!(
parked_fingerprint(&m, "elsewhere"),
Some(fingerprint("elsewhere-renewed"))
);
assert_eq!(in_the_file(&m), None);
}
#[test]
#[cfg_attr(
windows,
ignore = "W23: Claude Code's Credential Manager store, which a machine in memory plays"
)]
fn the_login_of_an_account_not_enrolled_is_refused_and_named() {
let m = machine("stow-not-enrolled");
m.api.owned_by("access-stranger-refresh", owner("stranger"));
leave(&m, &document("stranger-refresh"));
let vault = m.mem.vault().services();
let left = found(&m);
assert_eq!(
left.login,
Foreseen::NotEnrolled(owner("stranger")),
"the look says so first"
);
assert_eq!(
left.refusal().map(|refused| refused.code()),
Some("left_login_not_enrolled")
);
let refused = stow_now(&m).expect_err("refused");
assert_eq!(refused.code(), "left_login_not_enrolled");
let said = refused.to_string();
assert!(
said.contains("stranger@example.com")
&& said.contains("org-stranger")
&& said.contains("pitboard enroll <label> --sign-in")
&& said.contains("pitboard stow"),
"{said}"
);
assert_eq!(in_the_file(&m), Some(fingerprint("stranger-refresh")));
assert_eq!(m.mem.vault().services(), vault);
}
#[test]
#[cfg_attr(
windows,
ignore = "W23: Claude Code's Credential Manager store, which a machine in memory plays"
)]
fn a_file_with_no_login_in_it_is_deleted() {
let mcp = json!({"mcpOAuth": {"some-server": {"token": "t"}}}).to_string();
for (contents, dropped) in [
("{}", Vec::<&str>::new()),
(mcp.as_str(), vec!["mcpOAuth"]),
("", Vec::new()),
("not json", Vec::new()),
("[]", Vec::new()),
(r#"{"claudeAiOauth": {}}"#, vec!["claudeAiOauth"]),
] {
let m = machine("stow-no-login");
left_file(&m).plant(&m.service, contents);
assert_eq!(
provider::document(TOOL)
.behind(&m.ctx)
.map(|behind| behind.held),
Some(provider::Held::NoLogin),
"{contents:?}"
);
assert_eq!(
found(&m).login,
Foreseen::Kept(Kept::NoLogin),
"{contents:?}"
);
let (stowed, _) = stow_now(&m).expect("put away");
assert_eq!(stowed.kept, Kept::NoLogin, "{contents:?}");
assert_eq!(stowed.dropped, dropped, "{contents:?}");
assert!(left_file(&m).peek(&m.service).is_none(), "{contents:?}");
assert!(m.api.asked().is_empty());
}
}
#[test]
#[cfg_attr(
windows,
ignore = "W23: Claude Code's Credential Manager store, which a machine in memory plays"
)]
fn a_login_anthropic_refuses_for_good_goes_with_the_file() {
let m = machine("stow-refused");
m.api.renew_trouble("gone-refresh", Trouble::InvalidGrant);
leave(&m, &lapsed("gone-refresh"));
assert_eq!(found(&m).login, Foreseen::Untold);
assert!(m.api.asked().is_empty(), "an expired token is not sent");
let (stowed, _) = stow_now(&m).expect("put away");
assert_eq!(stowed.kept, Kept::Refused);
assert_eq!(in_the_file(&m), None);
}
#[test]
#[cfg_attr(
windows,
ignore = "W23: Claude Code's Credential Manager store, which a machine in memory plays"
)]
fn an_expired_login_is_renewed_and_written_back_before_anything_else() {
let m = machine("stow-renewed");
renews(&m, "left-refresh", "left-renewed");
m.api.owned_by("access-left-renewed", owner("stranger"));
leave(&m, &lapsed("left-refresh"));
let refused = stow_now(&m).expect_err("not enrolled");
assert_eq!(refused.code(), "left_login_not_enrolled");
assert!(
refused.to_string().ends_with(
"Pitboard deleted nothing, having renewed the login in the file and written it \
back there."
),
"{refused}"
);
assert_eq!(in_the_file(&m), Some(fingerprint("left-renewed")));
let held: Value =
serde_json::from_str(&left_file(&m).peek(&m.service).expect("still there"))
.expect("JSON");
assert_eq!(held["mcpOAuth"], document("x")["mcpOAuth"]);
enrolled(&m, "stranger");
let (stowed, _) = stow_now(&m).expect("put away once enrolled");
assert_eq!(
stowed.kept,
Kept::ParkedNow {
label: "stranger".into()
}
);
assert_eq!(
parked_fingerprint(&m, "stranger"),
Some(fingerprint("left-renewed"))
);
assert_eq!(in_the_file(&m), None);
}
#[test]
#[cfg_attr(
windows,
ignore = "W23: Claude Code's Credential Manager store, which a machine in memory plays"
)]
fn with_anthropic_out_of_reach_nothing_changes() {
let m = machine("stow-offline");
m.api
.token_trouble("access-unknown-refresh", Trouble::Offline);
leave(&m, &document("unknown-refresh"));
let before = state_file(&m);
let state = state::load(&m.ctx).expect("state");
let looked = find(&m.ctx, &state).expect_err("nobody to ask");
assert_eq!(looked.code(), "left_login_unidentified");
assert_eq!(looked.cause(), Some(Cause::Unreachable));
let seen = store::fingerprint(&left_file(&m).peek(&m.service).expect("there"));
let refused = stow_seen(&m, &seen).expect_err("nobody to ask");
assert_eq!(refused.code(), "left_login_unidentified");
assert_eq!(in_the_file(&m), Some(fingerprint("unknown-refresh")));
assert_eq!(state_file(&m), before);
let m = machine("stow-offline-renewing");
m.api.renew_trouble("lapsed-refresh", Trouble::Offline);
leave(&m, &lapsed("lapsed-refresh"));
let before = state_file(&m);
let refused = stow_now(&m).expect_err("nobody to renew it");
assert_eq!(refused.code(), "left_login_unidentified");
assert_eq!(in_the_file(&m), Some(fingerprint("lapsed-refresh")));
assert_eq!(state_file(&m).0, before.0);
}
#[test]
#[cfg_attr(
windows,
ignore = "W23: Claude Code's Credential Manager store, which a machine in memory plays"
)]
fn a_locked_keychain_or_an_unreadable_file_changes_nothing() {
let m = machine("stow-locked");
m.api.owned_by("access-there-again", owner("there"));
leave(&m, &document("there-again"));
let seen = found(&m).seen;
m.fault_live(Fault::Locked);
let state = state::load(&m.ctx).expect("state");
assert_eq!(
find(&m.ctx, &state).expect_err("locked").code(),
"credential_store_locked"
);
assert_eq!(
stow_seen(&m, &seen).expect_err("locked").code(),
"credential_store_locked"
);
assert_eq!(in_the_file(&m), Some(fingerprint("there-again")));
let m = machine("stow-unreadable");
leave(&m, &document("there-again"));
left_file(&m).fault(
&m.service,
Fault::UnreadableContents("permission denied".into()),
);
let state = state::load(&m.ctx).expect("state");
assert_eq!(
find(&m.ctx, &state).expect_err("unreadable").code(),
"credential_store_unreadable"
);
left_file(&m).heal(&m.service);
assert_eq!(in_the_file(&m), Some(fingerprint("there-again")));
}
#[test]
#[cfg_attr(
windows,
ignore = "W23: Claude Code's Credential Manager store, which a machine in memory plays"
)]
fn a_file_that_changed_since_it_was_confirmed_is_left_alone() {
let m = machine("stow-changed");
leave(&m, &document("here-refresh"));
let seen = found(&m).seen;
leave(&m, &document("here-again"));
let refused = stow_seen(&m, &seen).expect_err("it changed");
assert_eq!(refused.code(), "left_login_changed");
assert_eq!(in_the_file(&m), Some(fingerprint("here-again")));
left_file(&m).delete_everything();
assert_eq!(
stow_seen(&m, &seen).expect_err("it went").code(),
"left_login_changed"
);
}
#[test]
#[cfg_attr(
windows,
ignore = "W23: Claude Code's Credential Manager store, which a machine in memory plays"
)]
fn a_file_rewritten_meanwhile_is_left_as_it_was_written() {
for (point, label, left, parked) in [
("stow.identified", "elsewhere", "elsewhere-refresh", None),
(
"stow.kept",
"there",
"there-again",
Some(fingerprint("there-refresh")),
),
] {
let m = machine(&format!("stow-rewritten-{point}"));
enrolled(&m, "elsewhere");
m.api.owned_by(&format!("access-{left}"), owner(label));
leave(&m, &document(left));
let vault = m.mem.vault().services();
let refused = stow_while(&m, point, rewrites_the_file(&m, &document("new")))
.expect_err("it changed");
assert_eq!(refused.code(), "left_login_changed", "{point}");
assert!(
refused.to_string().ends_with("Pitboard changed nothing."),
"{point}: {refused}"
);
assert_eq!(in_the_file(&m), Some(fingerprint("new")), "{point}");
assert_eq!(m.mem.vault().services(), vault, "{point}");
assert_eq!(parked_fingerprint(&m, label), parked, "{point}");
}
}
#[test]
#[cfg_attr(
windows,
ignore = "W23: Claude Code's Credential Manager store, which a machine in memory plays"
)]
fn a_sign_in_before_the_file_goes_keeps_the_file_whose_login_it_counted_on() {
for (name, left) in [
("stow-stored-replaced", "here-refresh"),
("stow-second-sign-in-replaced", "here-again"),
] {
let m = machine(name);
m.api.owned_by("access-here-again", owner("here"));
m.api
.owned_by("access-elsewhere-refresh", owner("elsewhere"));
leave(&m, &document(left));
let refused = stow_while(
&m,
"stow.identified",
signs_in(&m, &document("elsewhere-refresh")),
)
.expect_err("the login stored changed");
assert_eq!(refused.code(), "stored_login_changed", "{name}");
assert!(
refused.to_string().ends_with("Pitboard changed nothing."),
"{name}: {refused}"
);
assert_eq!(in_the_file(&m), Some(fingerprint(left)), "{name}");
assert_eq!(m.live(), Some(document("elsewhere-refresh")), "{name}");
assert_eq!(parked_fingerprint(&m, "here"), None, "{name}");
}
}
#[test]
#[cfg_attr(
windows,
ignore = "W23: Claude Code's Credential Manager store, which a machine in memory plays"
)]
fn a_refusal_after_the_login_was_renewed_or_parked_says_so() {
let m = machine("stow-renewed-then-offline");
renews(&m, "left-refresh", "left-renewed");
m.api.token_trouble("access-left-renewed", Trouble::Offline);
leave(&m, &lapsed("left-refresh"));
let refused = stow_now(&m).expect_err("nobody to ask");
assert_eq!(refused.code(), "left_login_unidentified");
assert_eq!(refused.cause(), Some(Cause::Unreachable));
assert!(
refused.to_string().ends_with(
"Pitboard deleted nothing, having renewed the login in the file and written it \
back there."
),
"{refused}"
);
assert_eq!(in_the_file(&m), Some(fingerprint("left-renewed")));
let m = machine("stow-parked-then-rewritten");
enrolled(&m, "elsewhere");
m.api
.owned_by("access-elsewhere-refresh", owner("elsewhere"));
leave(&m, &document("elsewhere-refresh"));
let refused = stow_while(
&m,
"stow.kept",
rewrites_the_file(&m, &json!({"mcpOAuth": {}})),
)
.expect_err("it changed");
assert_eq!(refused.code(), "left_login_changed");
assert!(
refused.to_string().ends_with(
"Pitboard deleted nothing, having parked the login in the file for `elsewhere`."
),
"{refused}"
);
assert_eq!(
parked_fingerprint(&m, "elsewhere"),
Some(fingerprint("elsewhere-refresh"))
);
assert!(left_file(&m).peek(&m.service).is_some(), "left as written");
let m = machine("stow-parked-then-locked");
enrolled(&m, "elsewhere");
m.api
.owned_by("access-elsewhere-refresh", owner("elsewhere"));
leave(&m, &document("elsewhere-refresh"));
let refused = stow_while(&m, "stow.kept", locks_the_keychain(&m))
.expect_err("the keychain is locked");
assert_eq!(refused.code(), "credential_store_locked");
assert!(
refused.to_string().ends_with(
"Pitboard deleted nothing, having parked the login in the file for `elsewhere`."
),
"{refused}"
);
assert_eq!(
parked_fingerprint(&m, "elsewhere"),
Some(fingerprint("elsewhere-refresh"))
);
assert_eq!(in_the_file(&m), Some(fingerprint("elsewhere-refresh")));
}
fn lapsed_login_left(name: &str) -> Machine {
let m = machine(name);
enrolled(&m, "elsewhere");
renews(&m, "left-refresh", "left-renewed");
m.api.owned_by("access-left-renewed", owner("elsewhere"));
leave(&m, &lapsed("left-refresh"));
m
}
fn renewed(m: &Machine) -> bool {
m.api.asked().contains(&Asked::Renew("left-refresh".into()))
}
#[test]
#[cfg_attr(
windows,
ignore = "W23: Claude Code's Credential Manager store, which a machine in memory plays"
)]
fn a_write_lock_held_before_a_renewal_stops_it_with_the_refresh_token_unsent() {
let m = lapsed_login_left("stow-write-lock-before");
let writing = lock_dir(&write_target(&m));
let refused = stow_while(&m, "stow.identified", takes_the_write_lock(&m))
.expect_err("Claude Code is writing");
assert_eq!(refused.code(), "switch_in_progress");
assert!(
refused.to_string().ends_with("Pitboard changed nothing."),
"{refused}"
);
assert!(!renewed(&m), "{:?}", m.api.asked());
assert_eq!(in_the_file(&m), Some(fingerprint("left-refresh")));
std::fs::remove_dir(&writing).expect("the session lets go");
let (stowed, _) = stow_now(&m).expect("put away");
assert_eq!(
stowed.kept,
Kept::ParkedNow {
label: "elsewhere".into()
}
);
assert_eq!(
parked_fingerprint(&m, "elsewhere"),
Some(fingerprint("left-renewed"))
);
}
#[test]
#[cfg_attr(
windows,
ignore = "W23: Claude Code's Credential Manager store, which a machine in memory plays"
)]
fn a_write_lock_held_once_the_refresh_token_is_spent_is_waited_for() {
let m = lapsed_login_left("stow-write-lock-after");
let (stowed, warnings) =
fault::meanwhile("refresh.waited", lets_go_of_the_write_lock(&m), || {
stow_while(&m, "refresh.exchanged", takes_the_write_lock(&m))
})
.expect("put away");
assert!(warnings.is_empty(), "saved under the lock: {warnings:?}");
assert_eq!(
stowed.kept,
Kept::ParkedNow {
label: "elsewhere".into()
}
);
assert_eq!(
parked_fingerprint(&m, "elsewhere"),
Some(fingerprint("left-renewed"))
);
assert_eq!(in_the_file(&m), None);
}
#[test]
#[cfg_attr(
windows,
ignore = "W23: Claude Code's Credential Manager store, which a machine in memory plays"
)]
fn a_write_lock_never_let_go_of_does_not_cost_the_renewed_login() {
let m = lapsed_login_left("stow-write-lock-stuck");
let stuck = lock_dir(&write_target(&m));
let refused = stow_while(&m, "refresh.exchanged", move || {
let parent = stuck.parent().expect("the storage directory");
std::fs::create_dir_all(parent).expect("the storage directory is made");
let long_ago = std::time::SystemTime::now() - std::time::Duration::from_secs(3600);
std::fs::File::create(&stuck)
.and_then(|lock| lock.set_modified(long_ago))
.expect("something in the lock's place");
})
.expect_err("the lock is never had, to delete the file under");
assert_eq!(refused.code(), "switch_in_progress");
assert!(
refused.to_string().ends_with(
"Pitboard deleted nothing, having parked the login in the file for `elsewhere`."
),
"{refused}"
);
assert_eq!(in_the_file(&m), Some(fingerprint("left-renewed")));
assert_eq!(
parked_fingerprint(&m, "elsewhere"),
Some(fingerprint("left-renewed"))
);
}
#[test]
#[cfg_attr(
windows,
ignore = "W23: Claude Code's Credential Manager store, which a machine in memory plays"
)]
fn a_file_whose_other_keys_were_written_meanwhile_takes_the_renewed_login() {
let m = machine("stow-other-keys-written");
renews(&m, "left-refresh", "left-renewed");
m.api.owned_by("access-left-renewed", owner("stranger"));
leave(&m, &lapsed("left-refresh"));
let mut saved = lapsed("left-refresh");
saved["mcpOAuth"] = json!({"another-server": {"token": "saved meanwhile"}});
let refused = stow_while(&m, "refresh.exchanged", rewrites_the_file(&m, &saved))
.expect_err("not enrolled");
assert_eq!(refused.code(), "left_login_not_enrolled");
assert!(
refused.to_string().ends_with(
"Pitboard deleted nothing, having renewed the login in the file and written it \
back there."
),
"{refused}"
);
let held: Value =
serde_json::from_str(&left_file(&m).peek(&m.service).expect("still there"))
.expect("JSON");
assert_eq!(held["claudeAiOauth"]["refreshToken"], "left-renewed");
assert_eq!(held["mcpOAuth"], saved["mcpOAuth"]);
}
#[test]
#[cfg_attr(
windows,
ignore = "W23: Claude Code's Credential Manager store, which a machine in memory plays"
)]
fn a_keychain_that_locks_while_the_login_is_renewed_does_not_lose_it() {
let m = lapsed_login_left("stow-locks-while-renewing");
let refused = stow_while(&m, "refresh.exchanged", locks_the_keychain(&m))
.expect_err("the keychain is locked");
assert_eq!(refused.code(), "credential_store_locked");
assert!(
refused.to_string().ends_with(
"Pitboard deleted nothing, having parked the login in the file for `elsewhere`."
),
"{refused}"
);
assert_eq!(in_the_file(&m), Some(fingerprint("left-renewed")));
m.mem.live().heal(&m.service);
let (stowed, _) = stow_now(&m).expect("put away");
assert_eq!(
stowed.kept,
Kept::AlreadyParked {
label: "elsewhere".into()
}
);
assert_eq!(in_the_file(&m), None);
}
#[test]
#[cfg_attr(
windows,
ignore = "W23: Claude Code's Credential Manager store, which a machine in memory plays"
)]
fn a_renewed_login_that_cannot_be_written_back_is_said_to_be_spent() {
const SPENT: &str = "Pitboard deleted nothing. It renewed the login the file held and \
could not write the renewed login back there, so that login no \
longer works.";
let m = lapsed_login_left("stow-signed-in-while-renewing");
let refused = stow_while(
&m,
"refresh.exchanged",
rewrites_the_file(&m, &document("signed-in")),
)
.expect_err("another login is in the file");
assert_eq!(refused.code(), "left_login_changed");
assert!(refused.to_string().ends_with(SPENT), "{refused}");
assert_eq!(in_the_file(&m), Some(fingerprint("signed-in")));
assert_eq!(parked_fingerprint(&m, "elsewhere"), None);
assert!(
!m.mem
.vault()
.services()
.iter()
.any(|service| service.contains("-renewing-")),
"the copy is let go of: {:?}",
m.mem.vault().services()
);
}
#[test]
#[cfg_attr(
windows,
ignore = "W23: Claude Code's Credential Manager store, which a machine in memory plays"
)]
fn a_renewed_login_the_file_cannot_take_yet_is_kept_for_the_next_change() {
const KEPT: &str = "Pitboard deleted nothing. It renewed the login the file held and \
keeps the renewed login until the next change writes it back \
there.";
let m = lapsed_login_left("stow-unwritable-while-renewing");
let (file, service) = (left_file(&m), m.service.clone());
let fails = {
let (file, service) = (std::sync::Arc::clone(&file), service.clone());
move || file.fault(&service, Fault::FailWrite("disk full".into()))
};
let refused =
stow_while(&m, "refresh.exchanged", fails).expect_err("the file cannot be written");
assert_eq!(refused.code(), "credential_write_failed");
assert!(refused.to_string().ends_with(KEPT), "{refused}");
assert_eq!(in_the_file(&m), Some(fingerprint("left-refresh")));
m.api.renew_trouble("left-refresh", Trouble::InvalidGrant);
file.heal(&service);
let refused = stow_now(&m).expect_err("the next change wrote the file");
assert_eq!(refused.code(), "left_login_changed");
assert_eq!(in_the_file(&m), Some(fingerprint("left-renewed")));
let (stowed, _) = stow_now(&m).expect("put away");
assert_eq!(
stowed.kept,
Kept::ParkedNow {
label: "elsewhere".into()
}
);
assert_eq!(
parked_fingerprint(&m, "elsewhere"),
Some(fingerprint("left-renewed"))
);
assert_eq!(in_the_file(&m), None);
}
#[test]
#[cfg_attr(
windows,
ignore = "W23: Claude Code's Credential Manager store, which a machine in memory plays"
)]
fn nothing_behind_the_store_is_nothing_to_put_away() {
let m = machine("stow-nothing");
let state = state::load(&m.ctx).expect("state");
assert_eq!(find(&m.ctx, &state).expect("a look"), None);
leave(&m, &document("here-refresh"));
m.mem.live().delete_everything();
assert_eq!(find(&m.ctx, &state).expect("a look"), None);
}
#[test]
#[cfg_attr(
windows,
ignore = "W23: Claude Code's Credential Manager store, which a machine in memory plays"
)]
fn the_look_says_what_putting_away_would_do_and_changes_nothing() {
let m = machine("stow-look");
enrolled(&m, "elsewhere");
m.api
.owned_by("access-elsewhere-refresh", owner("elsewhere"));
leave(&m, &document("elsewhere-refresh"));
let before = state_file(&m);
let vault = m.mem.vault().services();
let left = found(&m);
assert_eq!(left.path, live::credential_file(&m.ctx));
assert_eq!(
left.login,
Foreseen::Kept(Kept::ParkedNow {
label: "elsewhere".into()
})
);
assert_eq!(left.dropped, ["mcpOAuth"]);
assert_eq!(
left.seen,
store::fingerprint(&document("elsewhere-refresh").to_string())
);
assert_eq!(state_file(&m), before);
assert_eq!(m.mem.vault().services(), vault);
assert_eq!(in_the_file(&m), Some(fingerprint("elsewhere-refresh")));
}
#[test]
#[cfg_attr(
windows,
ignore = "W23: Claude Code's Credential Manager store, which a machine in memory plays"
)]
fn killed_anywhere_the_login_is_left_somewhere_and_putting_away_again_finishes() {
for point in ["stow.identified", "refresh.saved", "stow.kept"] {
let m = machine(&format!("stow-killed-{point}"));
enrolled(&m, "elsewhere");
renews(&m, "left-refresh", "left-renewed");
m.api.owned_by("access-left-renewed", owner("elsewhere"));
leave(&m, &lapsed("left-refresh"));
let seen = found(&m).seen;
let died = fault::killing(point, || stow_seen(&m, &seen));
assert_eq!(died.unwrap_err(), point);
let renewed = Some(fingerprint("left-renewed"));
let held = in_the_file(&m);
assert!(
held == Some(fingerprint("left-refresh"))
|| held == renewed
|| parked_fingerprint(&m, "elsewhere") == renewed,
"{point}: the login is nowhere"
);
let (stowed, _) = stow_now(&m).expect("put away again");
assert!(
matches!(
stowed.kept,
Kept::ParkedNow { .. } | Kept::AlreadyParked { .. }
),
"{point}: {:?}",
stowed.kept
);
assert_eq!(parked_fingerprint(&m, "elsewhere"), renewed, "{point}");
assert_eq!(in_the_file(&m), None, "{point}");
}
}
#[test]
#[cfg_attr(
windows,
ignore = "W23: Claude Code's Credential Manager store, which a machine in memory plays"
)]
fn killed_before_the_renewed_login_reached_the_file_the_next_change_writes_it_there() {
let m = lapsed_login_left("stow-killed-after-the-exchange");
let seen = found(&m).seen;
let died = fault::killing("refresh.exchanged", || stow_seen(&m, &seen));
assert_eq!(died.unwrap_err(), "refresh.exchanged");
m.api.renew_trouble("left-refresh", Trouble::InvalidGrant);
assert_eq!(in_the_file(&m), Some(fingerprint("left-refresh")));
let refused = stow_seen(&m, &seen).expect_err("the file changed");
assert_eq!(refused.code(), "left_login_changed");
assert_eq!(in_the_file(&m), Some(fingerprint("left-renewed")));
let (stowed, _) = stow_now(&m).expect("put away");
assert_eq!(
stowed.kept,
Kept::ParkedNow {
label: "elsewhere".into()
}
);
assert_eq!(
parked_fingerprint(&m, "elsewhere"),
Some(fingerprint("left-renewed"))
);
assert_eq!(in_the_file(&m), None);
assert!(
!m.mem
.vault()
.services()
.iter()
.any(|service| service.contains("-renewing-")),
"the copy is let go of: {:?}",
m.mem.vault().services()
);
assert_eq!(state::load(&m.ctx).expect("state").renewing, []);
}
#[test]
#[cfg_attr(
windows,
ignore = "W23: Claude Code's Credential Manager store, which a machine in memory plays"
)]
fn a_renewal_left_unsaved_is_saved_before_the_file_is_put_away() {
let m = lapsed_login_left("stow-left-unsaved");
let seen = found(&m).seen;
let died = fault::killing("refresh.exchanged", || stow_seen(&m, &seen));
assert_eq!(died.unwrap_err(), "refresh.exchanged");
m.api.renew_trouble("left-refresh", Trouble::InvalidGrant);
let (file, service) = (left_file(&m), m.service.clone());
file.fault(&service, Fault::FailWrite("disk full".into()));
let refused = stow_seen(&m, &seen).expect_err("the copy cannot be saved");
assert_eq!(refused.code(), "credential_write_failed");
assert!(
refused.to_string().ends_with("Pitboard changed nothing."),
"{refused}"
);
let sent = Asked::Renew("left-refresh".into());
assert_eq!(m.api.asked().iter().filter(|a| **a == sent).count(), 1);
assert_eq!(in_the_file(&m), Some(fingerprint("left-refresh")));
file.heal(&service);
stow_seen(&m, &seen).expect_err("the next change wrote the file");
assert_eq!(in_the_file(&m), Some(fingerprint("left-renewed")));
}
#[test]
#[cfg_attr(
windows,
ignore = "W23: Claude Code's Credential Manager store, which a machine in memory plays"
)]
fn a_file_whose_renewal_waits_is_not_put_away_once_its_login_is_named() {
let m = machine("stow-renewal-waits-named");
enrolled(&m, "elsewhere");
renews(&m, "left-refresh", "left-renewed");
m.api.owned_by("access-left-renewed", owner("elsewhere"));
m.api
.token_trouble("access-left-refresh", Trouble::Unauthorized);
leave(&m, &document("left-refresh"));
let (file, service) = (left_file(&m), m.service.clone());
file.fault(&service, Fault::FailWrite("disk full".into()));
let refused = stow_now(&m).expect_err("the file cannot be written");
assert_eq!(refused.code(), "credential_write_failed");
m.api.renew_trouble("left-refresh", Trouble::InvalidGrant);
m.api.owned_by("access-left-refresh", owner("elsewhere"));
let refused = stow_now(&m).expect_err("a renewal of the file's login waits");
assert_eq!(refused.code(), "credential_write_failed");
assert!(
refused.to_string().ends_with("Pitboard changed nothing."),
"{refused}"
);
assert_eq!(in_the_file(&m), Some(fingerprint("left-refresh")));
assert_eq!(parked_fingerprint(&m, "elsewhere"), None);
file.heal(&service);
let refused = stow_now(&m).expect_err("the next change wrote the file");
assert_eq!(refused.code(), "left_login_changed");
let (stowed, _) = stow_now(&m).expect("put away");
assert_eq!(
stowed.kept,
Kept::ParkedNow {
label: "elsewhere".into()
}
);
assert_eq!(
parked_fingerprint(&m, "elsewhere"),
Some(fingerprint("left-renewed"))
);
let sent = Asked::Renew("left-refresh".into());
assert_eq!(m.api.asked().iter().filter(|a| **a == sent).count(), 1);
}
#[test]
#[cfg_attr(
windows,
ignore = "W23: Claude Code's Credential Manager store, which a machine in memory plays"
)]
fn putting_away_is_recorded_in_the_activity_log() {
let m = machine("stow-audit");
enrolled(&m, "elsewhere");
m.api
.owned_by("access-elsewhere-refresh", owner("elsewhere"));
leave(&m, &document("elsewhere-refresh"));
let pitboard = Pitboard::new(m.ctx.clone());
let left = pitboard.left_login().expect("a look").expect("left");
let done = pitboard.stow(&left.seen).expect("put away");
assert_eq!(
done.value.kept,
Kept::ParkedNow {
label: "elsewhere".into()
}
);
assert_eq!(
audit_lines(&m, "stow"),
[("elsewhere".to_string(), "parked".to_string())]
);
let read = pitboard.status(false).expect("a read");
assert!(
read.warnings.iter().all(|w| w.code() != "fallback_login"),
"{:?}",
read.warnings
);
m.api.owned_by("access-stranger-refresh", owner("stranger"));
leave(&m, &document("stranger-refresh"));
let left = pitboard.left_login().expect("a look").expect("left");
let refused = pitboard.stow(&left.seen).expect_err("not enrolled");
assert_eq!(refused.error.code(), "left_login_not_enrolled");
assert_eq!(
audit_lines(&m, "stow").last(),
Some(&(String::new(), "left_login_not_enrolled".to_string()))
);
}
#[test]
#[cfg_attr(
windows,
ignore = "W23: Claude Code's Credential Manager store, which a machine in memory plays"
)]
fn a_login_renewed_in_the_file_after_a_run_stopped_takes_the_spent_parks_place() {
for stop in ["killed", "locked"] {
let m = machine(&format!("stow-renewed-after-{stop}"));
enrolled(&m, "elsewhere");
m.api
.owned_by("access-elsewhere-refresh", owner("elsewhere"));
leave(&m, &document("elsewhere-refresh"));
let seen = found(&m).seen;
if stop == "killed" {
let died = fault::killing("stow.kept", || stow_seen(&m, &seen));
assert_eq!(died.unwrap_err(), "stow.kept");
} else {
let refused =
fault::meanwhile("stow.kept", locks_the_keychain(&m), || stow_seen(&m, &seen))
.expect_err("the keychain is locked");
assert_eq!(refused.code(), "credential_store_locked");
m.mem.live().heal(&m.service);
}
assert_eq!(
parked_fingerprint(&m, "elsewhere"),
Some(fingerprint("elsewhere-refresh")),
"{stop}"
);
assert_eq!(
in_the_file(&m),
Some(fingerprint("elsewhere-refresh")),
"{stop}"
);
m.api
.renew_trouble("elsewhere-refresh", Trouble::InvalidGrant);
m.api
.owned_by("access-elsewhere-renewed", owner("elsewhere"));
leave(&m, &renewed_later("elsewhere-renewed"));
let (stowed, _) = stow_now(&m).expect("put away again");
assert_eq!(
stowed.kept,
Kept::ParkedNow {
label: "elsewhere".into()
},
"{stop}"
);
assert_eq!(
parked_fingerprint(&m, "elsewhere"),
Some(fingerprint("elsewhere-renewed")),
"{stop}"
);
assert_eq!(in_the_file(&m), None, "{stop}");
let state = state::load(&m.ctx).expect("state");
let parked = state
.get(&m.key("elsewhere"))
.and_then(|a| a.parked.clone())
.expect("parked");
let held = park::load(
&m.ctx,
provider::document(TOOL),
&m.key("elsewhere"),
&parked,
)
.expect("it reads back");
assert_eq!(
held["claudeAiOauth"]["refreshToken"], "elsewhere-renewed",
"{stop}"
);
}
}
#[test]
#[cfg_attr(
windows,
ignore = "W23: Claude Code's Credential Manager store, which a machine in memory plays"
)]
fn a_session_renewing_the_login_in_the_file_meanwhile_waits_for_it() {
for (point, left, parked) in [
("refresh.saved", lapsed("left-refresh"), "left-renewed"),
("stow.kept", document("left-refresh"), "left-refresh"),
] {
let m = machine(&format!("stow-session-{point}"));
enrolled(&m, "elsewhere");
renews(&m, "left-refresh", "left-renewed");
m.api.owned_by("access-left-refresh", owner("elsewhere"));
m.api.owned_by("access-left-renewed", owner("elsewhere"));
leave(&m, &left);
let session = Session::new();
let done = stow_while(&m, point, renews_meanwhile(&m, left_file(&m), &session));
saves(&m, &left_file(&m), &session);
assert!(
session.waited.get(),
"{point}: a session renewed the login while Pitboard counted on it"
);
let (stowed, _) = done.expect("put away");
assert_eq!(
stowed.kept,
Kept::ParkedNow {
label: "elsewhere".into()
},
"{point}"
);
assert_eq!(
parked_fingerprint(&m, "elsewhere"),
Some(fingerprint(parked)),
"{point}"
);
assert_eq!(in_the_file(&m), None, "{point}");
assert!(
!lock_dir(&provider::claude::paths::refresh_lock(&m.ctx)).exists(),
"{point}: the refresh lock is let go of"
);
}
let m = machine("stow-session-before");
enrolled(&m, "elsewhere");
m.api.owned_by("access-left-refresh", owner("elsewhere"));
leave(&m, &document("left-refresh"));
let session = Session::new();
let refused = stow_while(
&m,
"stow.identified",
renews_meanwhile(&m, left_file(&m), &session),
)
.expect_err("it changed");
assert!(!session.waited.get());
assert_eq!(refused.code(), "left_login_changed");
assert_eq!(
in_the_file(&m),
Some(fingerprint("left-refresh-by-a-session"))
);
assert_eq!(parked_fingerprint(&m, "elsewhere"), None);
}
#[test]
#[cfg_attr(
windows,
ignore = "W23: Claude Code's Credential Manager store, which a machine in memory plays"
)]
fn a_login_of_an_account_not_enrolled_is_named_without_asking_whose_the_login_stored_is() {
let m = machine("stow-not-enrolled-stored-unknown");
m.sign_in(&document("here-renewed"));
m.api.token_trouble("access-here-renewed", Trouble::Offline);
m.api.owned_by("access-stranger-refresh", owner("stranger"));
leave(&m, &document("stranger-refresh"));
let left = found(&m);
let refused = stow_seen(&m, &left.seen).expect_err("not enrolled");
assert_eq!(left.login, Foreseen::NotEnrolled(owner("stranger")));
assert_eq!(refused.code(), "left_login_not_enrolled");
assert!(
m.api
.asked()
.iter()
.all(|asked| *asked != Asked::Owner("access-here-renewed".into())),
"{:?}",
m.api.asked()
);
assert_eq!(in_the_file(&m), Some(fingerprint("stranger-refresh")));
}
#[test]
#[cfg_attr(
windows,
ignore = "W23: Claude Code's Credential Manager store, which a machine in memory plays"
)]
fn a_lapsed_stored_login_is_renewed_to_tell_whose_it_is_and_the_file_put_away() {
let kept = Kept::ParkKept {
label: "there".into(),
};
let m = machine("stow-stored-lapsed");
m.sign_in(&lapsed("here-renewed"));
renews(&m, "here-renewed", "here-again");
m.api.owned_by("access-here-again", owner("here"));
m.api.owned_by("access-there-again", owner("there"));
leave(&m, &document("there-again"));
let left = found(&m);
assert_eq!(left.login, Foreseen::Kept(kept.clone()));
assert!(
!m.api.asked().contains(&Asked::Renew("here-renewed".into())),
"the look renews nothing"
);
let (stowed, _) = stow_seen(&m, &left.seen).expect("put away");
assert_eq!(stowed.kept, kept);
assert_eq!(in_the_file(&m), None);
assert_eq!(
m.live()
.and_then(|stored| stored["claudeAiOauth"]["refreshToken"]
.as_str()
.map(str::to_owned)),
Some("here-again".into())
);
assert_eq!(
parked_fingerprint(&m, "there"),
Some(fingerprint("there-refresh"))
);
}
#[test]
#[cfg_attr(
windows,
ignore = "W23: Claude Code's Credential Manager store, which a machine in memory plays"
)]
fn a_renewal_of_the_login_stored_that_stops_stow_is_said() {
for lost in [false, true] {
let m = machine(&format!("stow-stored-renewal-lost-{lost}"));
enrolled(&m, "elsewhere");
m.sign_in(&lapsed("here-renewed"));
renews(&m, "here-renewed", "here-again");
m.api.owned_by("access-here-again", owner("here"));
m.api.owned_by("access-left-refresh", owner("elsewhere"));
leave(&m, &document("left-refresh"));
if lost {
m.mem.vault().takes_on_stdin(16);
}
let refused = stow_while(&m, "refresh.exchanged", locks_the_keychain(&m))
.expect_err("the keychain is locked");
assert_eq!(refused.code(), "credential_store_locked", "lost: {lost}");
let ending = if lost {
"Run `claude`, sign in, then try again."
} else {
"Pitboard deleted nothing. It renewed the login Claude Code has stored and keeps \
the renewed login until the next change saves it there."
};
assert!(refused.to_string().ends_with(ending), "{refused}");
assert_eq!(in_the_file(&m), Some(fingerprint("left-refresh")));
if lost {
continue;
}
let (keychain, service) = m.live_store();
keychain.heal(&service);
m.api.renew_trouble("here-renewed", Trouble::InvalidGrant);
let (stowed, _) = stow_now(&m).expect("put away");
assert_eq!(
stowed.kept,
Kept::ParkedNow {
label: "elsewhere".into()
}
);
assert_eq!(in_the_file(&m), None);
let sent = Asked::Renew("here-renewed".into());
assert_eq!(m.api.asked().iter().filter(|a| **a == sent).count(), 1);
}
}
#[test]
#[cfg_attr(
windows,
ignore = "W23: Claude Code's Credential Manager store, which a machine in memory plays"
)]
fn a_login_with_no_refresh_token_left_to_expire_is_dead() {
let m = machine("stow-no-refresh-token");
leave(
&m,
&json!({"claudeAiOauth": {
"accessToken": "access-without-a-refresh-token",
"expiresAt": (NOW - 60) * 1000,
}}),
);
assert_eq!(found(&m).login, Foreseen::Kept(Kept::Refused));
let (stowed, _) = stow_now(&m).expect("put away");
assert_eq!(stowed.kept, Kept::Refused);
assert_eq!(in_the_file(&m), None);
assert!(m.api.asked().is_empty(), "{:?}", m.api.asked());
}
#[test]
#[cfg_attr(
windows,
ignore = "W23: Claude Code's Credential Manager store, which a machine in memory plays"
)]
fn putting_away_says_what_signs_claude_code_in_some_other_way() {
let m = machine("stow-overridden");
let config = m.ctx_home().join(".claude");
std::fs::create_dir_all(&config).expect("a config dir");
std::fs::write(
config.join("settings.json"),
json!({"apiKeyHelper": "/usr/local/bin/get-key"}).to_string(),
)
.expect("settings");
leave(&m, &document("here-refresh"));
let pitboard = Pitboard::new(m.ctx.clone());
let left = pitboard.left_login().expect("a look").expect("left");
let done = pitboard.stow(&left.seen).expect("put away");
let codes: Vec<&str> = done.warnings.iter().map(Warning::code).collect();
assert_eq!(codes, ["auth_overridden"]);
assert_eq!(in_the_file(&m), None);
}
}