How Earendil π Detects Self-Updates for Yarn
TLDR: Earendil π detects Yarn installations by inspecting the script path for /yarn/ or /.yarn/ segments, then generates a self-update command using yarn global add --ignore-scripts with conditional removal of the previous package version if the package name has changed.
Earendil π is a coding agent CLI that manages its own lifecycle across different package managers. When users install the tool globally via Yarn, the application must identify this environment and produce the correct upgrade sequence to avoid permission errors or script execution vulnerabilities. The detection and command generation logic lives in the configuration module, where specific string patterns in the installation path trigger Yarn-specific update behavior.
Detecting Yarn Installations via Path Inspection
The detectInstallMethod() function in packages/coding-agent/src/config.ts determines the package manager by analyzing the resolved path of the currently executing script. This approach avoids relying on environment variables that might not persist across shell sessions.
The Yarn Path Signature
At lines 71-73 of config.ts, the function checks if the script path contains either "/yarn/" or "/.yarn/". If either substring is present, the function returns "yarn" as the install method identifier.
// packages/coding-agent/src/config.ts (lines 71-73)
if (scriptPath.includes("/yarn/") || scriptPath.includes("/.yarn/")) {
return "yarn";
}
This detection works because Yarn installs global packages into directories that include these path segments, whether using Yarn Classic or Yarn Berry (Plug'n'Play) configurations.
Building the Self-Update Command for Yarn
Once the install method is identified as "yarn", the getSelfUpdateCommandForMethod() function constructs the appropriate shell commands. Located at lines 119-125 in packages/coding-agent/src/config.ts, this function handles both standard updates and package name migrations.
The Command Construction Logic
The function uses helper utilities makeSelfUpdateCommand() and makeSelfUpdateCommandStep() to assemble the final command string. For Yarn installations, it produces a command that adds the new package globally while bypassing lifecycle scripts:
// Yarn case (packages/coding-agent/src/config.ts lines 119-125)
return makeSelfUpdateCommand(
makeSelfUpdateCommandStep("yarn", ["global", "add", "--ignore-scripts", updatePackageName]),
updatePackageName === installedPackageName
? undefined
: makeSelfUpdateCommandStep("yarn", ["global", "remove", installedPackageName]),
);
Handling Package Name Changes
The logic includes a conditional removal step. When updatePackageName differs from installedPackageName—such as when migrating between npm scopes or package names—the function prepends a yarn global remove command for the old package. This prevents orphaned installations and ensures the CLI binary resolves to the new package location.
Security Considerations with --ignore-scripts
The generated command explicitly includes the --ignore-scripts flag. This prevents post-install scripts from executing during the update process, mitigating supply chain attack vectors where malicious code might run with the user's privileges during the upgrade.
Practical Examples of Yarn Self-Updates
When you run pi self-update on a Yarn-installed instance, the tool executes precisely the commands generated by the logic above.
Standard update (same package name):
# Detects Yarn installation, executes:
yarn global add --ignore-scripts pi-coding-agent
Update with package migration (different names):
# Detects Yarn installation and name change, executes:
yarn global remove old-scope/pi-coding-agent
yarn global add --ignore-scripts new-scope/pi-coding-agent
Testing the Yarn Detection Logic
The Yarn self-update behavior is validated in packages/coding-agent/test/plan-mode-utils.test.ts. These tests verify that the command builder correctly sequences removal before addition when the package identifiers differ, ensuring that the $PATH resolution always points to the latest version without conflicts.
Summary
- Path-based detection: Earendil π identifies Yarn installations by checking for
"/yarn/"or"/.yarn/"substrings in the script path viadetectInstallMethod(). - Command generation: The
getSelfUpdateCommandForMethod()function inpackages/coding-agent/src/config.tsconstructs the update commands. - Safe upgrades: The generated command uses
yarn global add --ignore-scriptsto prevent arbitrary script execution during updates. - Migration support: If the package name changes, the logic automatically prepends a
yarn global removecommand for the previous package. - Test coverage: The behavior is verified in
packages/coding-agent/test/plan-mode-utils.test.ts.
Frequently Asked Questions
How does Earendil π determine if it was installed using Yarn?
Earendil π checks the resolved file path of the running script for the substrings "/yarn/" or "/.yarn/". If either pattern is found in the path returned by detectInstallMethod(), the CLI registers the install method as "yarn" and uses Yarn-specific logic for subsequent self-update operations.
What happens when the package name changes during a self-update?
When updatePackageName differs from installedPackageName, the getSelfUpdateCommandForMethod() function generates a two-step command. It first removes the old package using yarn global remove, then installs the new package with yarn global add --ignore-scripts. This ensures no binary conflicts occur in the global Yarn bin directory.
Why does Earendil π use the --ignore-scripts flag for Yarn updates?
The --ignore-scripts flag is included in the Yarn global add command to prevent the execution of post-install lifecycle scripts during the update process. This security measure protects users from potentially malicious code that could otherwise run automatically when upgrading the package.
Where is the Yarn self-update detection logic tested?
The Yarn-specific self-update logic is tested in packages/coding-agent/test/plan-mode-utils.test.ts. These tests confirm that the command builder correctly orders removal and installation steps when package names differ, and that the --ignore-scripts flag is present in the generated command structure.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →