# Case Study: Issue #129 - Extra Quotes in Release Notes

## Issue Summary

Release v0.8.40 displays extra quotes around the release notes:

**Expected:**

```
Test patch release (instant)
```

**Actual:**

```
''Test patch release (instant)''
```

Source: https://github.com/link-foundation/test-anywhere/releases/tag/v0.8.40

## Timeline/Sequence of Events

1. **Manual Workflow Trigger**: User triggered workflow_dispatch with:
   - `release_mode`: `instant`
   - `bump_type`: `patch`
   - `description`: `Test patch release (instant)`

2. **Workflow Execution** (workflow run 20138655912):
   - Workflow received input correctly: `description: 'Test patch release (instant)'`
   - Called `version-and-commit.mjs` with named arguments

3. **version-and-commit.mjs** (line 191):

   ```javascript
   await $`node scripts/instant-version-bump.mjs --bump-type "${bumpType}" --description "${description}"`;
   ```

   - Input: `description = "Test patch release (instant)"`
   - **ISSUE**: `command-stream` library adds shell escaping
   - Output command: `node scripts/instant-version-bump.mjs --bump-type "patch" --description "'Test patch release (instant)'"`

4. **instant-version-bump.mjs** receives:
   - `description = "'Test patch release (instant)'"`
   - Writes to CHANGELOG.md with the quotes included (line 79)

5. **CHANGELOG.md** contains:

   ```markdown
   - 'Test patch release (instant)'
   ```

6. **create-github-release.mjs**:
   - Reads CHANGELOG.md entry with quotes
   - Creates GitHub release with notes containing quotes

7. **format-github-release.mjs**:
   - Calls format-release-notes.mjs to clean up the release

8. **format-release-notes.mjs** (line 122):

   ```javascript
   .replace(/^['"]|['"]$/g, '') // Remove leading/trailing single or double quotes
   ```

   - **ISSUE**: Only removes **unescaped** quotes from start/end
   - Does NOT remove the escaped quotes `\'` that appear in the GitHub API response
   - Result: `\''Test patch release (instant)''` remains in release body

## Root Causes

### Primary Root Cause: Command-Stream Shell Escaping

**Location**: `scripts/version-and-commit.mjs:191`

When `command-stream` library executes:

```javascript
await $`node scripts/instant-version-bump.mjs --bump-type "${bumpType}" --description "${description}"`;
```

It adds shell escaping to protect the string, wrapping it with single quotes:

```bash
--description "'Test patch release (instant)'"
```

This means the actual argument passed to `instant-version-bump.mjs` includes the quotes as part of the string value.

**Evidence from logs**:

```
Parsed configuration: {
  mode: 'instant',
  bumpType: 'patch',
  description: 'Test patch release (instant)'
}

# But the command executed is:
node scripts/instant-version-bump.mjs --bump-type "patch" --description "'Test patch release (instant)'"
```

### Secondary Root Cause: Inadequate Quote Cleaning

**Location**: `scripts/format-release-notes.mjs:122`

The cleanup regex only removes leading/trailing quotes:

```javascript
.replace(/^['"]|['"]$/g, '') // Remove leading/trailing single or double quotes
```

However, the GitHub API returns escaped quotes as `\'`, so the regex doesn't match them.

## Technical Analysis

### Why Command-Stream Adds Quotes

The `command-stream` library automatically adds shell escaping to protect arguments that might contain special characters (spaces, parentheses, etc.). When it sees `Test patch release (instant)` with spaces and parentheses, it wraps it in single quotes to ensure it's treated as a single argument:

```javascript
// Input to command-stream
description = "Test patch release (instant)"

// Shell command generated by command-stream
--description "'Test patch release (instant)'"

// Value received by the script
description = "'Test patch release (instant)'"
```

### Why Format-Release-Notes Can't Clean It

When GitHub's API returns the release body, it contains:

```
\''Test patch release (instant)''
```

The regex `/^['"]|['"]$/g` looks for literal quotes at the start/end, but the backslash-escaped quotes `\'` don't match this pattern.

## Proposed Solutions

### Solution 1: Remove Quotes from Template Literal (Recommended)

**File**: `scripts/version-and-commit.mjs:191-194`

Instead of passing `description` in quotes through command-stream, we can construct the command differently:

```javascript
// Current (problematic):
if (description) {
  await $`node scripts/instant-version-bump.mjs --bump-type "${bumpType}" --description "${description}"`;
} else {
  await $`node scripts/instant-version-bump.mjs --bump-type "${bumpType}"`;
}

// Fixed option 1: Use array argument style (avoids shell escaping)
const args = ['--bump-type', bumpType];
if (description) {
  args.push('--description', description);
}
await $(['node', 'scripts/instant-version-bump.mjs', ...args]);

// Fixed option 2: Pass via environment variable
if (description) {
  process.env.DESCRIPTION = description;
}
await $`node scripts/instant-version-bump.mjs --bump-type "${bumpType}"`;
```

**Pros**:

- Fixes root cause at the source
- No escaping issues
- Clean data flow

**Cons**:

- Changes how command-stream is used
- May require testing command-stream's array argument handling

### Solution 2: Strip Quotes in instant-version-bump.mjs

**File**: `scripts/instant-version-bump.mjs:44-45`

```javascript
// Current:
const { bumpType, description: descriptionArg } = config;
const description = descriptionArg || `Manual ${bumpType} release`;

// Fixed:
const { bumpType, description: descriptionArg } = config;
const description = (descriptionArg || `Manual ${bumpType} release`).replace(
  /^['"]|['"]$/g,
  ''
); // Remove surrounding quotes
```

**Pros**:

- Simple fix
- Handles the issue defensively at the receiving end

**Cons**:

- Treats symptom, not cause
- If user legitimately wants quotes, they get stripped

### Solution 3: Improve format-release-notes.mjs Quote Cleaning

**File**: `scripts/format-release-notes.mjs:119-129`

```javascript
// Current:
const cleanDescription = rawDescription
  .replace(/\\n/g, '\n')
  .replace(/^['"]|['"]$/g, '');
// ...

// Fixed:
const cleanDescription = rawDescription
  .replace(/\\n/g, '\n')
  .replace(/^\\'|'$/g, '') // Remove escaped quotes from start
  .replace(/^\\"|"$/g, '') // Remove escaped double quotes from start
  .replace(/^['"]|['"]$/g, ''); // Remove unescaped quotes
// ...
```

**Pros**:

- Catches the issue at formatting stage
- Defensive coding

**Cons**:

- Still doesn't fix the root cause
- Might miss other escaping patterns

### Solution 4: Combined Approach (Most Robust)

Implement both Solution 1 (fix the source) AND Solution 2 (defensive stripping):

1. Fix `version-and-commit.mjs` to avoid command-stream adding quotes
2. Add defensive quote stripping in `instant-version-bump.mjs`
3. Keep improved quote cleaning in `format-release-notes.mjs`

**Pros**:

- Defense in depth
- Handles edge cases
- Clean data flow with fallback protection

**Cons**:

- More changes required
- Slightly more complex

## Recommended Solution

**Solution 1** is recommended as the primary fix, with **Solution 2** as a defensive addition.

### Implementation Plan

1. Modify `scripts/version-and-commit.mjs` to pass arguments to command-stream without triggering shell escaping
2. Add defensive quote stripping to `scripts/instant-version-bump.mjs`
3. Test with various input strings including:
   - `Test patch release (instant)`
   - `Fix "critical" bug`
   - `Add 'new' feature`
   - `Release with spaces and (parentheses)`
4. Verify CHANGELOG.md entries are clean
5. Verify GitHub release notes are formatted correctly

## Related Issues & PRs

- Issue #126: Related to argument parsing in release workflow
- PR #128: Fixed argument parsing for all script invocations

## References

- [GitHub Discussion #26736: Character escaping for workflow command values](https://github.com/orgs/community/discussions/26736)
- [GitHub Discussion #26621: Escaping ${{}} in workflows](https://github.com/orgs/community/discussions/26621)
- [Bash Hackers Wiki: Quotes and escaping](https://flokoe.github.io/bash-hackers-wiki/syntax/quoting/)

## Artifacts

- `release-v0.8.40.json`: Full release data from GitHub API
- `recent-ci-runs.json`: Recent workflow runs
- `workflow-run-20138655912.log`: Complete logs from the release workflow run
