Silicon IAMDocs

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:

  1. SILICON_IAM_HOME: an explicit, exact IAM storage directory.
  2. The directory saved by iam config home <location> under the current default base.
  3. $SILICON_HOME/.silicon-iam if SILICON_HOME is present.
  4. $HOME/.silicon-iam otherwise.

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:

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:

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.