Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
217 changes: 217 additions & 0 deletions docs/case-studies/issue-123/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
# Case Study: Issue #123 - Badge Not Found

## Issue Summary

**Issue:** [#123 - badge not found](https://github.com/link-assistant/agent/issues/123)
**Release:** [js-v0.8.4](https://github.com/link-assistant/agent/releases/tag/js-v0.8.4)
**Date:** 2026-01-16
**Type:** Bug

The GitHub release notes for version 0.8.4 displayed a "404 badge not found" error from shields.io instead of the expected npm version badge.

## Timeline of Events

### 1. Issue #121 - Release Style Problem
**Date:** 2026-01-13

User reported that release style didn't match the template repository:
- Wrong release name format (should use `[js]` prefix)
- Wrong changelog path (should use `js/CHANGELOG.md`)
- Release descriptions lacked actual changelog content

### 2. PR #122 - Fix Implementation
**Date:** 2026-01-13 to 2026-01-16

PR #122 was created to fix issue #121. Key changes in `scripts/create-github-release.mjs`:

```javascript
// Before (line 26):
const changelogPath = prefix === 'rust-' ? './rust/CHANGELOG.md' : './CHANGELOG.md';

// After:
const changelogPath =
prefix === 'rust-'
? './rust/CHANGELOG.md'
: prefix === 'js-'
? './js/CHANGELOG.md'
: './CHANGELOG.md';

// Before (line 49):
const releaseName = prefix ? `${prefix.replace(/-$/, '')} ${version}` : version;

// After:
const releaseName = prefix
? `[${prefix.replace(/-$/, '')}] ${version}`
: version;
```

### 3. Release js-v0.8.4
**Date:** 2026-01-16 06:26:26Z

- PR #122 merged to main
- CI/CD pipeline triggered
- Version bump: 0.8.3 → 0.8.4
- GitHub release created with tag `js-v0.8.4`
- Release name: `[js] 0.8.4` ✅
- Changelog content: correct ✅
- NPM badge: **404 badge not found** ❌

## Root Cause Analysis

### The Bug Location

**File:** `scripts/format-github-release.mjs` (line 82)

```javascript
const tag = `${prefix}v${version}`;
// ...
await $`node scripts/format-release-notes.mjs --release-id "${releaseId}" --release-version "${tag}" ...`;
```

The `--release-version` argument receives the **full tag** (`js-v0.8.4`), not just the version (`0.8.4`).

**File:** `scripts/format-release-notes.mjs` (lines 192-193)

```javascript
const versionWithoutV = version.replace(/^v/, '');
const npmBadge = `[![npm version](https://img.shields.io/badge/npm-${versionWithoutV}-blue.svg)](...)`;
```

The regex `^v` only removes a leading `v`, so:
- Input: `js-v0.8.4`
- After `.replace(/^v/, '')`: `js-v0.8.4` (unchanged - no leading v!)
- Badge URL: `https://img.shields.io/badge/npm-js-v0.8.4-blue.svg`

### Why This Fails

Shields.io static badges use dashes (`-`) as delimiters in the URL format:

```
https://img.shields.io/badge/LABEL-MESSAGE-COLOR
```

When the version is `js-v0.8.4`, the URL becomes:
```
https://img.shields.io/badge/npm-js-v0.8.4-blue
├─┘ ├┘ └─────┬──────┘
LABEL │ Interpreted as COLOR
MESSAGE
```

Shields.io interprets this as:
- **Label:** `npm`
- **Message:** `js`
- **Color:** `v0.8.4-blue` (invalid color!)

Result: 404 badge not found error.

### Working vs Broken URL Comparison

| URL | Result |
|-----|--------|
| `https://img.shields.io/badge/npm-0.8.4-blue.svg` | ✅ Shows `npm | 0.8.4` |
| `https://img.shields.io/badge/npm-js-v0.8.4-blue.svg` | ❌ Shows `404 | badge not found` |

### Why Template Repository Works

The template repository (`link-foundation/js-ai-driven-development-pipeline-template`) works correctly because it:
1. Uses simple version tags (`v0.3.0`) without prefix
2. The version passed to format-release-notes.mjs is just the version number

In this repository (`link-assistant/agent`):
1. Uses prefixed tags (`js-v0.8.4`, `rust-v1.0.0`) for multi-language support
2. The full tag is incorrectly passed as the version

## Proposed Solutions

### Solution 1: Pass Pure Version (Recommended)

**Change in `format-github-release.mjs`:**

```javascript
// Before (line 82):
await $`node scripts/format-release-notes.mjs --release-id "${releaseId}" --release-version "${tag}" ...`;

// After:
await $`node scripts/format-release-notes.mjs --release-id "${releaseId}" --release-version "v${version}" ...`;
```

This passes `v0.8.4` instead of `js-v0.8.4`, and the existing regex in `format-release-notes.mjs` handles the `v` prefix correctly.

### Solution 2: Update Regex in format-release-notes.mjs

**Change in `format-release-notes.mjs`:**

```javascript
// Before (line 192):
const versionWithoutV = version.replace(/^v/, '');

// After - handle both js-v and v prefixes:
const versionWithoutV = version.replace(/^(js-|rust-)?v/, '');
```

### Solution 3: URL-encode the Badge Text

If dashes must be preserved, use shields.io URL encoding (`--` for literal dash):

```javascript
const encodedVersion = versionWithoutV.replace(/-/g, '--');
const npmBadge = `[![npm version](https://img.shields.io/badge/npm-${encodedVersion}-blue.svg)](...)`;
```

### Recommended Approach

**Solution 1** is recommended because:
1. Minimal code change
2. Keeps format-release-notes.mjs simple and generic
3. The caller knows the context (prefix) and should clean it up

## Evidence and Data

### Files Collected

- `data/release-info.json` - Release metadata from GitHub API
- `data/pr-122-info.json` - PR #122 details and commits
- `data/pr-122-diff.txt` - PR #122 code changes
- `data/issue-121-details.txt` - Original issue that triggered the fix
- `data/ci-run-21057802787.log` - CI workflow logs showing the release process
- `screenshots/badge-not-found.png` - Screenshot showing the 404 badge error

### Key Log Evidence

From CI run logs:
```
Release Format GitHub release notes Formatting release notes for js-v0.8.4...
Release Format GitHub release notes ℹ️ Found Patch Changes section
Release Format GitHub release notes ℹ️ Looking up PR for commit afcd2f8 (from changelog)
Release Format GitHub release notes ✅ Found PR #122 containing commit
Release Format GitHub release notes - Added shields.io npm badge <-- Badge added but URL is wrong
```

### Badge URL Test Results

```bash
# Broken URL
$ curl -s "https://img.shields.io/badge/npm-js-v0.8.4-blue.svg" | grep -o 'aria-label="[^"]*"'
aria-label="404: badge not found"

# Working URL
$ curl -s "https://img.shields.io/badge/npm-0.8.4-blue.svg" | grep -o 'aria-label="[^"]*"'
aria-label="npm: 0.8.4"
```

## References

- [Issue #123](https://github.com/link-assistant/agent/issues/123) - This bug report
- [Issue #121](https://github.com/link-assistant/agent/issues/121) - Original style issue that triggered the buggy fix
- [PR #122](https://github.com/link-assistant/agent/pull/122) - The PR that introduced this bug
- [Release js-v0.8.4](https://github.com/link-assistant/agent/releases/tag/js-v0.8.4) - Affected release
- [Shields.io Static Badge Docs](https://shields.io/badges/static-badge) - Badge URL format specification
- [Template Repository](https://github.com/link-foundation/js-ai-driven-development-pipeline-template) - Reference implementation

## Lessons Learned

1. **Test with all prefixes:** When modifying release scripts that support multiple prefixes (js-, rust-), test all variants.
2. **Understand URL encoding:** Special characters like dashes have meaning in shields.io URLs.
3. **Trace data flow:** The bug wasn't in the modified files, but in how data flowed between scripts.
4. **Regression testing:** The fix for #121 inadvertently broke the badge because the version format changed.
15 changes: 15 additions & 0 deletions docs/case-studies/issue-123/data/issue-121-details.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
title: Release was done not in expected style
state: CLOSED
author: konard
labels: bug, documentation
comments: 0
assignees:
projects:
milestone:
number: 121
--
We should use [js] prefix in name, version is fine.

But the description of the release must be like in https://github.com/link-foundation/js-ai-driven-development-pipeline-template

Please use all latest best practices of https://github.com/link-foundation/js-ai-driven-development-pipeline-template for our JavaScript CI/CD. If we have something even better - please file issue to https://github.com/link-foundation/js-ai-driven-development-pipeline-template with proposal.
56 changes: 56 additions & 0 deletions docs/case-studies/issue-123/data/pr-122-diff.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
diff --git a/js/.changeset/fix-release-style.md b/js/.changeset/fix-release-style.md
new file mode 100644
index 0000000..d09eb9d
--- /dev/null
+++ b/js/.changeset/fix-release-style.md
@@ -0,0 +1,11 @@
+---
+'@link-assistant/agent': patch
+---
+
+Fix GitHub release style to match template repository standards
+
+- Fix release name format to use `[js]` prefix instead of `js ` (e.g., `[js] 0.8.4` instead of `js 0.8.4`)
+- Fix changelog path for js releases to use `js/CHANGELOG.md` instead of root `CHANGELOG.md`
+- This ensures release descriptions contain actual changelog content with PR links and npm badges
+
+Fixes #121
diff --git a/scripts/create-github-release.mjs b/scripts/create-github-release.mjs
index 749a73a..69fcc4f 100644
--- a/scripts/create-github-release.mjs
+++ b/scripts/create-github-release.mjs
@@ -61,12 +61,20 @@ console.log(`Creating GitHub release for ${tag}...`);

try {
// Read CHANGELOG.md - check prefix for appropriate changelog location
- const changelogPath = prefix === 'rust-' ? './rust/CHANGELOG.md' : './CHANGELOG.md';
+ // js- prefix uses js/CHANGELOG.md, rust- prefix uses rust/CHANGELOG.md, others use root
+ const changelogPath =
+ prefix === 'rust-'
+ ? './rust/CHANGELOG.md'
+ : prefix === 'js-'
+ ? './js/CHANGELOG.md'
+ : './CHANGELOG.md';
let changelog = '';
try {
changelog = readFileSync(changelogPath, 'utf8');
} catch {
- console.log(`No changelog found at ${changelogPath}, using default release notes`);
+ console.log(
+ `No changelog found at ${changelogPath}, using default release notes`
+ );
}

// Extract changelog entry for this version
@@ -87,7 +95,10 @@ try {
// Create release using GitHub API with JSON input
// This avoids shell escaping issues that occur when passing text via command-line arguments
// (Previously caused apostrophes like "didn't" to appear as "didn'''" in releases)
- const releaseName = prefix ? `${prefix.replace(/-$/, '')} ${version}` : version;
+ // Release name format: "[prefix] version" for prefixed releases (e.g., "[js] 0.8.3"), just version otherwise
+ const releaseName = prefix
+ ? `[${prefix.replace(/-$/, '')}] ${version}`
+ : version;
const payload = JSON.stringify({
tag_name: tag,
name: releaseName,
1 change: 1 addition & 0 deletions docs/case-studies/issue-123/data/pr-122-info.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"author":{"id":"MDQ6VXNlcjE0MzE5MDQ=","is_bot":false,"login":"konard","name":"Konstantin Diachenko"},"body":"## Summary\n- Fix release name format to use `[js]` prefix (e.g., `[js] 0.8.4` instead of `js 0.8.4`)\n- Fix changelog path for js releases to use `js/CHANGELOG.md` instead of root `CHANGELOG.md`\n- This ensures release descriptions contain actual changelog content with PR links and npm badges\n\n## Problem\nThe release script was using incorrect changelog path and release name format:\n1. **Wrong changelog path**: For `js-` prefixed releases, it was reading `./CHANGELOG.md` (root) instead of `./js/CHANGELOG.md`\n2. **Wrong name format**: Used `js 0.8.3` format instead of `[js] 0.8.3`\n\nThis caused releases to show a generic `Release X.X.X` description instead of the actual changelog content.\n\n## Solution\nUpdated `scripts/create-github-release.mjs` to:\n1. Read the correct changelog file based on the prefix\n2. Format release names with brackets around the prefix\n\n## Test plan\n- [ ] Verify next release uses `[js]` prefix format in the release name\n- [ ] Verify next release description contains actual changelog content\n- [ ] Verify PR link and npm badge appear in release notes\n\nFixes #121\n\n---\n🤖 Generated with [Claude Code](https://claude.com/claude-code)","commits":[{"authoredDate":"2026-01-13T21:03:41Z","authors":[{"email":"drakonard@gmail.com","id":"MDQ6VXNlcjE0MzE5MDQ=","login":"konard","name":"konard"}],"committedDate":"2026-01-13T21:03:41Z","messageBody":"Adding CLAUDE.md with task information for AI processing.\nThis file will be removed when the task is complete.\n\nIssue: https://github.com/link-assistant/agent/issues/121","messageHeadline":"Initial commit with task details","oid":"c662d6518214a71b186e6b9a14f165067b9a45ed"},{"authoredDate":"2026-01-13T21:07:35Z","authors":[{"email":"drakonard@gmail.com","id":"MDQ6VXNlcjE0MzE5MDQ=","login":"konard","name":"konard"},{"email":"noreply@anthropic.com","id":"MDQ6VXNlcjgxODQ3","login":"claude","name":"Claude Opus 4.5"}],"committedDate":"2026-01-13T21:07:35Z","messageBody":"- Fix release name format to use [js] prefix (e.g., \"[js] 0.8.4\")\n- Fix changelog path for js releases to use js/CHANGELOG.md\n- Ensures releases have proper descriptions with PR links and npm badges\n\nFixes #121\n\nCo-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>","messageHeadline":"fix: align GitHub release style with template repository","oid":"afcd2f83ac180e5668eed315e915a2f0bac917a6"},{"authoredDate":"2026-01-13T21:11:14Z","authors":[{"email":"drakonard@gmail.com","id":"MDQ6VXNlcjE0MzE5MDQ=","login":"konard","name":"konard"},{"email":"noreply@anthropic.com","id":"MDQ6VXNlcjgxODQ3","login":"claude","name":"Claude Opus 4.5"}],"committedDate":"2026-01-13T21:11:14Z","messageBody":"Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>","messageHeadline":"chore: remove automated setup file","oid":"9c45b0443b7b72c1782fd5721d30debfe107af09"}],"files":[{"path":"js/.changeset/fix-release-style.md","additions":11,"deletions":0},{"path":"scripts/create-github-release.mjs","additions":14,"deletions":3}],"mergedAt":"2026-01-16T06:26:26Z","title":"fix: align GitHub release style with template repository"}
1 change: 1 addition & 0 deletions docs/case-studies/issue-123/data/release-info.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"author":{"id":"MDM6Qm90NDE4OTgyODI=","login":"github-actions[bot]"},"body":"Fix GitHub release style to match template repository standards\n- Fix release name format to use `[js]` prefix instead of `js ` (e.g., `[js] 0.8.4` instead of `js 0.8.4`)\n- Fix changelog path for js releases to use `js/CHANGELOG.md` instead of root `CHANGELOG.md`\n- This ensures release descriptions contain actual changelog content with PR links and npm badges\n\nFixes #121\n\n**Related Pull Request:** #122\n\n---\n\n[![npm version](https://img.shields.io/badge/npm-js-v0.8.4-blue.svg)](https://www.npmjs.com/package/@link-assistant/agent/v/js-v0.8.4)","createdAt":"2026-01-16T06:28:24Z","name":"[js] 0.8.4","publishedAt":"2026-01-16T06:29:04Z","tagName":"js-v0.8.4"}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
55 changes: 55 additions & 0 deletions experiments/test-badge-url.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#!/usr/bin/env node

/**
* Test script to verify shields.io badge URL generation
*
* This tests the fix for issue #123 where the badge URL was incorrectly
* generated with the tag prefix (js-v0.8.4) instead of just the version (v0.8.4)
*
* Run: node experiments/test-badge-url.mjs
*/

const PACKAGE_NAME = '@link-assistant/agent';

// Test cases
const testCases = [
{ version: 'v0.8.4', description: 'Correct: version with v prefix only' },
{ version: 'js-v0.8.4', description: 'Incorrect: full tag with js- prefix' },
{ version: '0.8.4', description: 'Edge case: version without v prefix' },
{ version: 'rust-v1.0.0', description: 'Incorrect: full tag with rust- prefix' },
];

console.log('Testing shields.io badge URL generation\n');
console.log('='.repeat(80) + '\n');

for (const { version, description } of testCases) {
// Current code logic
const versionWithoutV = version.replace(/^v/, '');
const badgeUrl = `https://img.shields.io/badge/npm-${versionWithoutV}-blue.svg`;
const npmUrl = `https://www.npmjs.com/package/${PACKAGE_NAME}/v/${versionWithoutV}`;

console.log(`Test: ${description}`);
console.log(` Input version: "${version}"`);
console.log(` After .replace(/^v/, ''): "${versionWithoutV}"`);
console.log(` Badge URL: ${badgeUrl}`);

// Check if URL will work
const parts = versionWithoutV.split('-');
const isValid = parts.length === 1 || (parts.length === 3 && /^\d+$/.test(parts[0]));

// A simple heuristic: if the version contains dashes that aren't part of semver,
// shields.io will misinterpret it
const hasProblem = /^[a-z]+-/.test(versionWithoutV);

if (hasProblem) {
console.log(` Result: ❌ WILL FAIL - shields.io interprets dashes as delimiters`);
console.log(` Parsed as: LABEL="npm", MESSAGE="${parts[0]}", COLOR="${parts.slice(1).join('-')}"`);
} else {
console.log(` Result: ✅ Should work correctly`);
}
console.log('');
}

console.log('='.repeat(80));
console.log('\nFix: Pass "v${version}" instead of "${tag}" to format-release-notes.mjs');
console.log('This ensures the version never has the prefix (js-, rust-) in the badge URL.');
12 changes: 12 additions & 0 deletions js/.changeset/fix-badge-url.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
'@link-assistant/agent': patch
---

Fix shields.io badge URL in GitHub release notes

- Fixed badge URL generation that was broken by tag prefixes (js-, rust-)
- The `format-github-release.mjs` script now passes `v${version}` instead of the full tag
- This ensures the badge URL contains only the version number (e.g., `0.8.4`) without prefix
- See `docs/case-studies/issue-123` for detailed root cause analysis

Fixes #123
5 changes: 4 additions & 1 deletion scripts/format-github-release.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,10 @@ try {
console.log(`Formatting release notes for ${tag}...`);
// Pass the trigger commit SHA for PR detection
// This allows proper PR lookup even if the changelog doesn't have a commit hash
await $`node scripts/format-release-notes.mjs --release-id "${releaseId}" --release-version "${tag}" --repository "${repository}" --commit-sha "${commitSha}"`;
// IMPORTANT: Pass version with v-prefix only (e.g., "v0.8.4"), NOT the full tag (e.g., "js-v0.8.4")
// The format-release-notes.mjs script uses this for shields.io badge URL which uses dashes as delimiters
// See: docs/case-studies/issue-123 for detailed root cause analysis
await $`node scripts/format-release-notes.mjs --release-id "${releaseId}" --release-version "v${version}" --repository "${repository}" --commit-sha "${commitSha}"`;
console.log(`\u2705 Formatted release notes for ${tag}`);
}
} catch (error) {
Expand Down