Local state and concurrent CLI use
The CLI stores profiles, sessions and updater state under $SILICON_HOME/.silicon-iam when SILICON_HOME is set, otherwise ~/.silicon-iam. A home may be shared by concurrent CLI processes; production sessions and each testing-environment session remain separate.
State changes lock the complete read/modify/write operation across processes and merge into the latest document. Readers see a complete old or new JSON document, never a truncated intermediate write. Each write creates a unique temporary file, syncs it, then atomically renames it into place. On Unix, new directories are 0700, files are 0600 from creation, and the containing directory is synced after rename.
Each profile/environment session has its own transition lock. Refresh re-reads the session after acquiring that lock and keeps it through idempotency-key reservation, the network exchange and credential commit. A concurrent command uses the newly refreshed session instead of exchanging the old refresh token again. Login commits and local/remote logout use the same lock, so a refresh cannot resurrect a logged-out session or overwrite a later login. An uncertain refresh or remote logout retains its original idempotency key for an exact retry.
Do not delete credentials.lock or the session-*.lock files while CLI processes are running. Locks are released automatically when a process exits; the empty files are intentionally retained to keep a stable locking identity.
Version 1.4.1: storage base and overrides
The storage directory is selected in this order:
SILICON_IAM_HOME: an explicit, exact IAM storage directory.- The directory saved by
iam config home <location>under the current default base. $SILICON_HOME/.silicon-iamifSILICON_HOMEis present.$HOME/.silicon-iamotherwise.
SILICON_HOME replaces the user-home base; it is not the credential directory
itself. It also works when HOME is unset. iam config home requires an existing
directory and saves its selection in .silicon-iam/.silicon-iam-home under
SILICON_HOME (or HOME when unset). Each base has its own saved selection.
An explicit SILICON_IAM_HOME still wins over that saved selection.
SILICON_HOME=/path/to/silicon iam config show
SILICON_HOME=/path/to/silicon iam config home /existing/private/iam-store
SILICON_IAM_HOME=/exact/private/iam-store iam config show
Changing the base selects a different store; existing credentials are not moved. The Rust client is stateless and does not create a local credential store.
Upgrading an existing IAM home
On Unix, the IAM home must be owned by the current user with mode 0700. Older
CLI installations may have created it with mode 0755; 1.2.0 and newer intentionally
reject that directory even if its credential file is already 0600. This
requires a one-time permission repair, not a new login or deleting credentials.
Before changing permissions, stop other CLI processes and inspect the exact
directory selected using the precedence above (iam config show reports it
when the store is accessible).
Confirm it is the intended IAM-only directory, is not a symbolic link, and is
owned by your current user. Do not change permissions on a shared directory,
another user's home, or an unexpected link target. If the ownership or path is
wrong, resolve that deliberately or select a new private IAM home instead.
Only after those checks, repair the default home with:
chmod 700 ~/.silicon-iam
For a verified custom home, apply chmod 700 to its exact quoted path instead.
Do not use recursive chmod, sudo, or broad paths for this repair. Retry the
original command after the repair; the stored profiles and sessions are kept.
Credential, config and lock paths must be regular files, not symbolic links, devices or hard links; they must be owned by the current user and not writable by others. IAM rejects unsafe paths without changing link targets. Directory-relative no-follow operations keep writes anchored to the verified home. Windows uses the user's profile-directory access controls and rejects symbolic-link/reparse-point state paths; Unix permission modes do not apply there.
Use a local filesystem with working file locks and atomic rename. Do not share one credential home through a sync service or a filesystem that does not preserve those semantics. Separate homes are also useful when agents should not share credentials.
Diagnosing an unsuccessful logout
A local logout reports success only after the selected profile/environment's credential removal has been persisted. A nonzero exit or a process terminated by a signal is not a successful removal. A retained session after such a failure is different from a session remaining after a confirmed successful removal. Do not silently retry and discard the first failure's evidence.
Version 1.2.1 adds phase-specific diagnostics: Local credential removal could not be confirmed. identifies an unsuccessful local-removal
attempt, while IAM confirmed remote logout, but local credential removal could not be confirmed. distinguishes a completed remote logout from a subsequent
local error. These messages preserve the underlying error and exit status;
they do not claim the local file remained unchanged, because a persistence
error can occur after the atomic rename.
For an individual failing command, retain:
iam --version, the operating system, and whether the home uses a local filesystem or a shared/synced volume;- the selected profile and production/test environment UUID, plus the logout
mode (
logout,logout --all, orlogout --local-only); - its exit code, any termination signal reported by the shell/process runner, and the exact success message if one was printed;
- its stderr, including any CLI context and service request ID, reviewed and sanitized before sharing; and
- whether another login, refresh, logout, or process was using that same home and selected session at the time.
In a shell, save $? immediately after the invocation, before running another
command. A process runner should retain each child's exit code or signal and
stderr separately, rather than only counting failures. Disable automatic
maintenance with SILICON_IAM_AUTO_UPDATE=false for a deliberate diagnostic
invocation if you need to isolate the command from post-command update output.
Do not run a remote logout again merely to gather diagnostics: it changes
server state. Preserve the original evidence first, then choose recovery based
on the reported failure.
Never share credentials.json, tokens, OTPs, testing-environment keys, raw
process environments, or unreviewed command lines. A test environment UUID is
not its secret environment key. Redact private profile/path names and personal
details from diagnostics while keeping different sessions distinguishable.
The 1.2.0 external audit recorded one unsuccessful concurrent logout without its exit code or stderr; seven instrumented reruns then passed 672 calls. Its cause remains unresolved. Those results do not establish a lost successful write or prove that every concurrency/host failure is fixed.
Version 1.2.2: bounded Unix lock-open recovery
CLI 1.2.2 adds this hardening; it is not included in published 1.2.1.
The investigation reproduced five unsuccessful commands across 480
concurrent local logouts using the installed 1.2.1 CLI. No command falsely
reported success, and no successful credential removal was observed lost.
One baseline batch contained two failures; four instrumented batches contained
three. A pass-through macOS syscall observer captured those three failures as
openat returning ENOENT for a lock: the pinned directory was still owned,
private and live, and an immediate subsequent lookup found the regular,
single-linked 0600 lock file. That establishes the failed syscall and the
CLI's fail-fast handling, not the underlying cause or a kernel/APFS defect.
Version 1.2.2 retries only opening a lock file, and only after
Unix ENOENT or EINTR. It permits at most six open attempts, with delays of
1, 2, 4, 8 and 16 milliseconds: 31 milliseconds of scheduled backoff, not a
wall-clock deadline. Each retry uses the same pinned directory descriptor and
open flags. Before retrying, IAM verifies that the home remains live, owned
by the current user, private, and the same directory identified by its path.
Both blocking and nonblocking lock acquisition share this handling. Before and after acquiring a lock, IAM requires exactly one link and checks that the opened lock's device/inode identity still matches its name in the pinned directory. Unsafe paths, changed lock identities, other errors, and failures that exhaust the small retry budget still fail; there is no unlocked fallback. The retry does not repeat credential reads, JSON parsing, or state mutations. The lock-only single-link check does not reject an already-open JSON snapshot merely because a concurrent atomic replacement unlinked its old inode.
Pre-release local validation
The fixed local candidate, before the release version bump, passed five
unchanged runs of the supplied offline audit: 480/480 concurrent logout
commands and all 80 check groups passed.
One run was uninstrumented and four used the pass-through syscall observer.
No naturally occurring ENOENT was captured after the change, so these runs
do not establish that a natural failure was recovered by a retry.
Separate manual checks exercised the actual CLI with controlled faults:
- Five injected
ENOENTfailures succeeded on open attempt six; one injectedEINTRsucceeded on attempt two. - Persistent
ENOENTfailed with exit 2 after six opens;EACCESfailed with exit 2 after one open, without retry. Synthetic credentials stayed unchanged. - Hard-linked and FIFO lock files were rejected without changing credentials.
- Replacing a lock while the CLI waited for it caused exit 2 after acquisition of the old inode, without changing credentials or proceeding unlocked.
- Replacing the IAM home after an injected failure was rejected before the next open; original credentials stayed unchanged and the new home stayed empty.
No new automated tests were added; the supplied audit was run unchanged. The installed 1.2.1 binary was left untouched during these checks; validation used the fixed local candidate. Upgrade to 1.2.2 to obtain this hardening. The original failures remain evidence; these checks do not identify their underlying platform cause or prove that every host failure is fixed.