|
| 1 | +# Case Study: Rust CI/CD Failure - Yargs Reserved Word Conflict |
| 2 | + |
| 3 | +**Issue:** [#22](https://github.com/link-foundation/lino-objects-codec/issues/22) |
| 4 | +**Date of Failure:** 2026-01-08 |
| 5 | +**CI Run:** [#20828012412](https://github.com/link-foundation/lino-objects-codec/actions/runs/20828012412/job/59834479617) |
| 6 | + |
| 7 | +## Executive Summary |
| 8 | + |
| 9 | +The Rust CI/CD pipeline was failing during the "Auto Release" phase because the `create-github-release.mjs` script was unable to parse the `--version` command-line argument. The root cause was that `"version"` is a reserved word in yargs (the CLI argument parsing library used by `lino-arguments`), which caused the argument to be interpreted as yargs' built-in version display command instead of our custom option. |
| 10 | + |
| 11 | +## Timeline of Events |
| 12 | + |
| 13 | +1. **18:48:47 UTC** - CI pipeline triggered by push to main branch |
| 14 | +2. **18:48:51 UTC** - "Detect Changes" job completed successfully |
| 15 | +3. **18:48:58 - 18:49:48 UTC** - "Lint and Format Check", "Test", and "Build Package" jobs completed successfully |
| 16 | +4. **18:50:06 UTC** - "Auto Release" job started |
| 17 | +5. **18:50:11 UTC** - Git configured, changelog fragments detected (1 fragment, patch bump) |
| 18 | +6. **18:50:11 UTC** - Version check passed (new version 0.2.0 detected) |
| 19 | +7. **18:50:19 UTC** - Build and publish steps completed |
| 20 | +8. **18:50:19 UTC** - "Create GitHub Release" step started |
| 21 | +9. **18:50:24 UTC** - **FAILURE**: Script exited with "Missing required arguments" |
| 22 | + |
| 23 | +## Root Cause Analysis |
| 24 | + |
| 25 | +### The Error |
| 26 | + |
| 27 | +``` |
| 28 | +Auto Release Create GitHub Release Error: Missing required arguments |
| 29 | +Auto Release Create GitHub Release Usage: node scripts/create-github-release.mjs --version <version> --repository <repository> |
| 30 | +Auto Release Create GitHub Release ##[error]Process completed with exit code 1. |
| 31 | +``` |
| 32 | + |
| 33 | +### The Command Executed |
| 34 | + |
| 35 | +```bash |
| 36 | +node scripts/create-github-release.mjs \ |
| 37 | + --version "0.2.0" \ |
| 38 | + --repository "link-foundation/lino-objects-codec" \ |
| 39 | + --tag-prefix "rust-v" |
| 40 | +``` |
| 41 | + |
| 42 | +### Investigation |
| 43 | + |
| 44 | +The arguments were correctly passed to the script, but the script reported "Missing required arguments". This was puzzling because the arguments were clearly present in the command. |
| 45 | + |
| 46 | +Upon testing locally, we discovered that the parsed `version` value was `false` instead of `"0.2.0"`: |
| 47 | + |
| 48 | +```javascript |
| 49 | +Parsed config: { |
| 50 | + "version": false, // Expected: "0.2.0" |
| 51 | + "repository": "test/repo", |
| 52 | + "tagPrefix": "v" |
| 53 | +} |
| 54 | +``` |
| 55 | + |
| 56 | +Node.js also printed a warning: |
| 57 | + |
| 58 | +``` |
| 59 | +Warning: "version" is a reserved word. |
| 60 | +Please do one of the following: |
| 61 | +- Disable version with `yargs.version(false)` if using "version" as an option |
| 62 | +- Use the built-in `yargs.version` method instead (if applicable) |
| 63 | +- Use a different option key |
| 64 | +``` |
| 65 | + |
| 66 | +### Root Cause |
| 67 | + |
| 68 | +The `lino-arguments` library uses [yargs](https://yargs.js.org/) internally for CLI argument parsing. In yargs v17.2.0+, `--version` is a reserved command that displays the application version and exits. When a user defines a custom `version` option, yargs exhibits unexpected behavior - it returns `false` instead of the actual argument value. |
| 69 | + |
| 70 | +This is a known issue documented in: |
| 71 | +- [yargs/yargs#2064](https://github.com/yargs/yargs/issues/2064) |
| 72 | +- [CycloneDX/cdxgen#83](https://github.com/CycloneDX/cdxgen/issues/83) |
| 73 | + |
| 74 | +## Solution |
| 75 | + |
| 76 | +The fix is to call `.version(false)` on the yargs instance before defining the custom `version` option. This disables yargs' built-in `--version` handling and allows the custom option to be parsed correctly. |
| 77 | + |
| 78 | +### Before (Broken) |
| 79 | + |
| 80 | +```javascript |
| 81 | +const config = makeConfig({ |
| 82 | + yargs: ({ yargs, getenv }) => |
| 83 | + yargs |
| 84 | + .option('version', { |
| 85 | + type: 'string', |
| 86 | + default: getenv('VERSION', ''), |
| 87 | + describe: 'Version number (e.g., 1.0.0)', |
| 88 | + }) |
| 89 | + // ... other options |
| 90 | +}); |
| 91 | +``` |
| 92 | + |
| 93 | +### After (Fixed) |
| 94 | + |
| 95 | +```javascript |
| 96 | +const config = makeConfig({ |
| 97 | + yargs: ({ yargs, getenv }) => |
| 98 | + yargs |
| 99 | + .version(false) // Disable yargs built-in --version handling |
| 100 | + .option('version', { |
| 101 | + type: 'string', |
| 102 | + default: getenv('VERSION', ''), |
| 103 | + describe: 'Version number (e.g., 1.0.0)', |
| 104 | + }) |
| 105 | + // ... other options |
| 106 | +}); |
| 107 | +``` |
| 108 | + |
| 109 | +## Affected Files |
| 110 | + |
| 111 | +Three scripts were affected by this issue: |
| 112 | + |
| 113 | +1. `rust/scripts/create-github-release.mjs` - **Primary failure point** |
| 114 | +2. `rust/scripts/collect-changelog.mjs` |
| 115 | +3. `csharp/scripts/create-github-release.mjs` |
| 116 | + |
| 117 | +## Lessons Learned |
| 118 | + |
| 119 | +1. **Reserved Words in Libraries**: When using CLI parsing libraries like yargs, be aware of reserved words that may conflict with your option names. Common reserved words in yargs include `version`, `help`, `$0`. |
| 120 | + |
| 121 | +2. **Warning Messages**: Node.js and libraries often emit warnings before errors occur. In this case, the warning about "version" being a reserved word was available but not visible in the CI output until the failure occurred. |
| 122 | + |
| 123 | +3. **Local Testing**: Testing scripts locally with the same arguments used in CI can help identify argument parsing issues that aren't obvious from CI logs alone. |
| 124 | + |
| 125 | +4. **Error Messages Can Be Misleading**: The error "Missing required arguments" suggested the arguments weren't being passed, but the real issue was that they were being ignored due to a naming conflict. |
| 126 | + |
| 127 | +## Prevention Measures |
| 128 | + |
| 129 | +1. **Avoid Reserved Names**: Consider using alternative option names like `pkg-version`, `release-version`, or `ver` to avoid conflicts with reserved words. |
| 130 | + |
| 131 | +2. **Always Use `.version(false)`**: When defining a custom `version` option in yargs, always disable the built-in version handling first. |
| 132 | + |
| 133 | +3. **Add Integration Tests**: Create tests that verify the CLI scripts correctly parse all expected arguments. |
| 134 | + |
| 135 | +## References |
| 136 | + |
| 137 | +- [Yargs Documentation - version()](https://yargs.js.org/docs/#api-reference-version) |
| 138 | +- [yargs/yargs#2064 - Cannot have version as both option and command](https://github.com/yargs/yargs/issues/2064) |
| 139 | +- [CycloneDX/cdxgen#83 - Warning: "version" is a reserved word](https://github.com/CycloneDX/cdxgen/issues/83) |
0 commit comments