How Career-Ops update-system.mjs Checks Versions and Rolls Back Updates

The update-system.mjs script in Career-Ops checks for updates by comparing local and remote semantic versions using curl-based HTTP requests, and rolls back changes by restoring system files from timestamped backup branches while leaving user data untouched.

Career-Ops uses a self-updating mechanism to keep its system layer synchronized with the upstream repository without touching user data. The update-system.mjs file serves as the core component that manages version verification through the check command and disaster recovery through the rollback command.

Version Checking Workflow

The check command determines whether the local Career-Ops installation is current by comparing the local VERSION file against remote sources. This process respects user preferences, handles offline scenarios, and outputs machine-readable JSON for integration with the CLI.

Dismiss-Flag and Local Version Resolution

Before querying remote servers, the script checks for a .update-dismissed file in the repository root. If existsSync(join(ROOT, '.update-dismissed')) returns true, the script immediately emits {"status":"dismissed"} and exits (lines 35-40).

If no dismiss flag exists, the script reads the local version via localVersion(), which calls parseVersionFile. This helper strips any "release-please" markers from the VERSION file content and normalizes it to a clean semver string (lines 39-48).

Remote Version Fetching

Because Node’s fetch API does not function inside the sandboxed environment, update-system.mjs uses a curlGet wrapper to perform two independent HTTP requests (lines 52-68):

  • Raw VERSION file: Fetched from RAW_VERSION_URL to get the canonical version string.
  • GitHub Releases API: Queried at RELEASES_API to retrieve the latest published release tag.

Both requests return null on network errors, ensuring the script never crashes due to connectivity issues.

Semantic Version Parsing and Comparison

The script extracts semver strings using the regex SEMVER_RE = /^v?(\d+\.\d+\.\d+)$/i (lines 50-57). It parses versions from both the raw file and the release API, then selects the higher of the two remote versions as the target.

The compareVersions function performs a numeric component-wise comparison (lines 99-103). If the local version is greater than or equal to the remote version, the script outputs {"status":"up-to-date"}; otherwise, it returns {"status":"update-available"} along with a trimmed changelog (lines 104-109).

Offline and Error Handling

If both curlGet calls return null, the script emits {"status":"offline"} (lines 78-88). If the requests succeed but no parsable version is found in either response, it returns {"status":"no-remote-version"}. These graceful degradation paths ensure the updater never blocks the user due to temporary network issues.

Rollback Workflow

When an update introduces breaking changes, the rollback command restores the system layer to its pre-update state using Git backup branches created during the last successful apply operation.

Backup Branch Selection

The script identifies candidate branches using git('for-each-ref', …) to list all local branches matching backup-pre-update-* (lines 66-88). It selects the newest branch by commit date or embedded timestamp, favoring the pattern backup-pre-update-${version}-${stamp}. If no backup branches exist, the script prints an error and exits with code 1 (lines 70-74).

System Path Restoration

For each path defined in SYSTEM_PATHS (which excludes user data per DATA_CONTRACT.md), the script executes git checkout <backup> -- <path> to restore the file state from the backup branch (lines 96-104). This targets only system-layer files, ensuring personal data remains untouched.

Handling Missing Paths

If a path exists in the current working tree but not in the backup branch, the catch block at lines 105-122 handles the divergence. The script executes git rm to stage the deletion and rmSync to remove the file from the filesystem. This cleanup covers files that were added after the backup was created, preventing orphaned files from lingering after rollback.

Commit and Safety Guarantees

After successfully checking out paths, the script stages them with git add (lines 126-127) and attempts to commit with a message indicating the rollback (lines 128-132). Commit failures are silently ignored because the working tree may already be clean if the user runs rollback twice.

The backup branch naming convention encodes both the version and timestamp, making rollbacks deterministic. Because SYSTEM_PATHS strictly defines the scope of restoration, user directories are never modified during this process.

Practical Usage Examples


# Check whether a newer version exists

node update-system.mjs check

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

# Apply the update (creates backup branch before modifying system)

node update-system.mjs apply

# Roll back to the previous state after a faulty update

node update-system.mjs rollback

# → Restores files from backup-pre-update-1.6.0-<timestamp> and commits changes

The check command is safe to run frequently and produces JSON output suitable for programmatic parsing by the Career-Ops CLI.

Summary

  • Dismissible notifications: Creating .update-dismissed suppresses update checks until the file is removed.
  • Dual-source verification: Queries both the raw VERSION file and GitHub Releases API to determine the latest available version.
  • Sandbox compatibility: Uses curl CLI instead of Node fetch to work within restricted environments.
  • Deterministic rollback: Restores from timestamped backup branches (backup-pre-update-*) affecting only paths defined in SYSTEM_PATHS.
  • Safe cleanup: Removes files added post-update using both git rm and rmSync to maintain repository consistency.

Frequently Asked Questions

What happens if Career-Ops is offline during a version check?

If both curlGet requests to RAW_VERSION_URL and RELEASES_API return null due to network failure, update-system.mjs outputs {"status":"offline"} and exits gracefully. This prevents the updater from falsely reporting that no updates exist or crashing due to connection timeouts.

How does update-system.mjs ensure user data is never rolled back?

The script strictly operates on paths listed in the SYSTEM_PATHS constant, which references only system-layer files as defined in DATA_CONTRACT.md. During rollback, it runs git checkout exclusively against these paths from the backup branch, ensuring user directories and personal data remain untouched throughout the restoration process.

Can I skip update notifications temporarily?

Yes. Creating an empty file named .update-dismissed in the repository root causes the check command to return {"status":"dismissed"} immediately without querying remote servers. This suppresses update prompts until the file is deleted, allowing users to defer updates during critical work periods.

What is the naming convention for backup branches?

Backup branches follow the pattern backup-pre-update-${version}-${stamp}, where version is the semver being backed up from and stamp is a numeric timestamp. The rollback logic selects the newest branch by commit date, ensuring the system restores to the most recent stable state prior to the last update.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →