# How update-system.mjs Handles Version Checking, Applying Updates, Dismissing, and Rollbacks in Career-Ops

> Discover how update-system.mjs in Career-Ops manages version checks, updates, dismissals, and rollbacks. Learn about its safe, atomic system-layer updates with lock files and backup branches.

- Repository: [Santiago Fernández de Valderrama/career-ops](https://github.com/santifer/career-ops)
- Tags: internals
- Published: 2026-06-07

---

**The `update-system.mjs` script in the Career-Ops repository provides four sub-commands—`check`, `apply`, `dismiss`, and `rollback`—that enable safe, atomic system-layer updates while strictly isolating user data through lock files, backup branches, and path-specific guards.**

The `update-system.mjs` module serves as the canonical maintenance utility for the Career-Ops repository. Operating exclusively on the **system layer**, it ensures user-layer files such as [`cv.md`](https://github.com/santifer/career-ops/blob/main/cv.md) or [`config/profile.yml`](https://github.com/santifer/career-ops/blob/main/config/profile.yml) remain untouched during version upgrades. Written as a self-contained Node.js ES module in the repository root, it implements a safety-first architecture that prevents concurrent runs and enables full recovery via Git-based backup branches.

## Version Checking with the `check` Command

The `check` command determines whether the local installation lags behind the remote canonical version.

### Local and Remote Resolution

The script reads the local `VERSION` file via the `localVersion()` function, then fetches two remote sources in parallel: the raw upstream `VERSION` file and the latest GitHub release via the API (as implemented in lines 59-67). It parses both responses and selects the highest semantic version using the internal `compareVersions` helper.

### Status Emissions

The command outputs a JSON object with one of five statuses: `up-to-date`, `update-available`, `offline`, `no-remote-version`, or `dismissed`. If a `.update-dismissed` flag exists, the function returns immediately with `{"status": "dismissed"}` without hitting the network (lines 60-64).

## Applying Updates Safely with `apply`

The `apply` command executes a multi-stage, atomic update process that modifies only paths defined in the `SYSTEM_PATHS` constant.

### Pre-Update Safety Measures

Before touching any files, the script creates a lock file named `.update-lock` to prevent overlapping invocations (lines 55-61). It then generates a backup branch named `backup-pre-update-<localVersion>` to preserve the pre-update state (lines 66-73).

### Atomic Update Execution

The update proceeds through the following guarded steps:

1. **Fetch upstream**: Runs `git fetch` against the canonical remote (lines 75-78).
2. **Checkout system paths**: Checks out **only** the paths listed in `SYSTEM_PATHS`, leaving all other files untouched (lines 80-108).
3. **Verify user-layer integrity**: Validates that no files in `USER_PATHS` were modified during the operation. If any user-layer file is touched, the procedure aborts and reverts all changes (lines 110-173).
4. **Dependency refresh**: Executes `npm install` to synchronize any new dependencies introduced in the update (lines 174-178).
5. **Commit and cleanup**: Stages the updated system files, clears the `.update-dismissed` flag if present, and commits with a descriptive message (lines 179-194).
6. **Lock removal**: Deletes the `.update-lock` file in a `finally` block to ensure cleanup even if an error occurs (lines 195-199).

## Dismissing Update Notifications

The `dismiss` command allows users to silence update prompts without applying the upgrade.

When invoked, the script writes a timestamp to `.update-dismissed` and exits (lines 190-194). Subsequent `check` calls detect this file and immediately return the `dismissed` status, bypassing remote version lookups until the flag is cleared by a successful `apply` operation.

## Recovery via `rollback`

The `rollback` command restores the system to the state captured by the most recent backup branch.

### Backup Restoration Process

The script identifies the latest `backup-pre-update-*` branch using `git for-each-ref`. For each entry in `SYSTEM_PATHS`, it attempts `git checkout <backup> -- <path>`. If a path did not exist in the backup, the script removes it from the working tree using `git rm` followed by `rmSync` (lines 210-276). Finally, it stages the restored files, commits a rollback message, and prints a summary indicating how many paths were restored versus removed (lines 277-282).

## Practical Usage Examples

Check for available updates (typically run on startup):

```bash
node update-system.mjs check

# {"status":"update-available","local":"1.6.0","remote":"1.7.0","changelog":"..."}

```

Apply the update after user confirmation:

```bash
node update-system.mjs apply

# Backup branch created: backup-pre-update-1.6.0

# Fetching latest from upstream...

# Updating system files...

# Update complete: v1.6.0 → v1.7.0

# Updated 45 system paths.

# Rollback available: node update-system.mjs rollback

```

Silence the update prompt:

```bash
node update-system.mjs dismiss

# "Update check dismissed. Run \"node update-system.mjs check\" to check manually."

```

Recover from a failed or unwanted update:

```bash
node update-system.mjs rollback

# Rolling back to: backup-pre-update-1.6.0

# Rollback complete. Restored 45 path(s) from backup-pre-update-1.6.0, removed 0 path(s).

# Your data (CV, profile, tracker, reports) was not affected.

```

## Summary

- **`check`**: Compares local `VERSION` against remote sources using `compareVersions`, returning JSON status codes including `dismissed` when `.update-dismissed` exists.
- **`apply`**: Creates a `.update-lock` and `backup-pre-update-<version>` branch, checks out only `SYSTEM_PATHS`, validates `USER_PATHS` integrity, runs `npm install`, and commits atomically.
- **`dismiss`**: Persists a timestamp to `.update-dismissed`, causing future checks to skip network requests.
- **`rollback`**: Restores files from the latest `backup-pre-update-*` branch, removing any new paths that did not exist in the backup.

## Frequently Asked Questions

### What happens if user-layer files are modified during an apply operation?

The script validates `USER_PATHS` after checking out `SYSTEM_PATHS` (lines 110-173). If any user-layer file is detected as modified, the operation aborts, reverts all staged changes, and exits with an error, leaving the working directory in its original state.

### How long does a dismissed update remain hidden?

The dismissal persists indefinitely until the user runs `node update-system.mjs apply` successfully, which automatically removes the `.update-dismissed` flag, or until the user manually deletes the `.update-dismissed` file from the repository root.

### Can you rollback to any previous version or only the immediate last one?

The `rollback` command selects the most recent `backup-pre-update-*` branch via `git for-each-ref` sorting. While the script is designed to recover the immediate previous state, manual Git operations on specific backup branches allow recovery to any prior version created by previous `apply` runs.

### Does the apply command handle dependency updates?

Yes. After updating system files, `apply` automatically executes `npm install` (lines 174-178) to install any new dependencies declared in the updated [`package.json`](https://github.com/santifer/career-ops/blob/main/package.json), ensuring the runtime environment matches the new system version.