feat(tui): highlight executed commands as bash; show inline duration

- Apply shell syntax highlighting to executed command lines in history cells
- Append running duration dimmed inline on the first command line
- Render full command via highlighter instead of plain text
- Prune obsolete local docs and test artifact files

Refs: fix/strip-osc8-in-markdown
This commit is contained in:
James Peter
2025-08-29 10:50:37 +10:00
parent a30c0198ad
commit 38dc45a1de
4 changed files with 50 additions and 317 deletions
-1
View File
@@ -1 +0,0 @@
ok
-154
View File
@@ -1,154 +0,0 @@
# Sccache Cache Hit Investigation Report
## Issue Summary
The GitHub Actions workflow is showing 0% cache hits despite using `mozilla-actions/[email protected]` with proper configuration. The cache location shows `ghac` (GitHub Actions Cache) with a consistent hash: `8a6df4cb573bcfbf8c8e0bad63dc2287eedbfcc340fd86fbbdfa3b71d52e1c25`.
## Current Configuration Analysis
### Workflow Setup (release.yml)
```yaml
# Line 124-128: Setup sccache action
- name: Setup sccache (GHA backend)
uses: mozilla-actions/[email protected]
with:
version: v0.10.0
token: ${{ secrets.GITHUB_TOKEN }}
# Line 131-148: Environment configuration
- Sets SCCACHE_GHA_ENABLED=true
- Sets SCCACHE_IDLE_TIMEOUT=1800
- Sets RUSTC_WRAPPER=sccache
- Configures OS-specific SCCACHE_DIR
# Line 151-155: Export GH cache credentials
- Exports ACTIONS_CACHE_URL
- Exports ACTIONS_RUNTIME_TOKEN
# Line 228-240: Build process
- Restarts sccache server to avoid race conditions
- Falls back to local cache if GHAC fails
- Shows stats before and after build
```
## Root Cause Analysis
### 1. **Hash Consistency Issue**
The hash `8a6df4cb573bcfbf8c8e0bad63dc2287eedbfcc340fd86fbbdfa3b71d52e1c25` appears to be the same across all builds. This suggests it's NOT a compilation hash but rather a **cache namespace/version identifier**.
### 2. **Missing Cache Key Configuration**
The workflow is missing critical cache key configuration:
- **SCCACHE_GHA_CACHE_TO**: Not set (determines where to write cache)
- **SCCACHE_GHA_CACHE_FROM**: Not set (determines where to read cache)
- **SCCACHE_GHA_VERSION**: Not set (used for cache busting)
Without these, sccache might be:
- Writing to a default location that changes per run
- Unable to read from previous cache entries
- Using a cache key that's too specific (includes run-specific data)
### 3. **GitHub Actions Cache Service Behavior**
The GitHub Actions cache service is **immutable** - once a (key, version) pair is reserved, it cannot be overwritten. This means:
- If the cache key is too specific (includes timestamps, run IDs), every build creates a new cache entry
- Subsequent builds can't find matching cache entries
### 4. **Path Dependencies**
Sccache requires **absolute paths to match** for cache hits. In GitHub Actions:
- The workspace path includes the run number: `/home/runner/work/code/code`
- If paths vary between runs, cache hits won't occur
### 5. **Incremental Compilation Interference**
The workflow sets `CARGO_INCREMENTAL="0"` (line 215), which is correct. However, certain crate types cannot be cached:
- bin crates (which is what's being built)
- proc-macro crates
- cdylib/dylib crates
## Recommended Solutions
### Solution 1: Configure Cache Keys Explicitly
```yaml
- name: Setup sccache (GHA backend)
uses: mozilla-actions/[email protected]
with:
version: v0.10.0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Configure sccache cache keys
shell: bash
run: |
# Use a stable cache key based on target and Rust version
CACHE_KEY="sccache-${{ matrix.target }}-rust-1.89"
echo "SCCACHE_GHA_CACHE_TO=${CACHE_KEY}" >> "$GITHUB_ENV"
echo "SCCACHE_GHA_CACHE_FROM=${CACHE_KEY}" >> "$GITHUB_ENV"
# Version for cache busting when needed
echo "SCCACHE_GHA_VERSION=v1" >> "$GITHUB_ENV"
```
### Solution 2: Use Stable Build Paths
```yaml
- name: Setup stable build path
shell: bash
run: |
# Create a stable build directory
sudo mkdir -p /opt/rust-build
sudo chown -R $USER:$USER /opt/rust-build
# Symlink or copy project there
cp -r . /opt/rust-build/
cd /opt/rust-build
```
### Solution 3: Focus on Library Caching
Since binary crates have limited caching potential, focus on caching dependencies:
```yaml
env:
# Only cache dependencies, not workspace members
SCCACHE_CACHE_MULTIARCH: "1"
# Increase cache size for better hit rates
SCCACHE_CACHE_SIZE: "10G"
```
### Solution 4: Diagnostic Improvements
Add better diagnostics to understand what's happening:
```yaml
- name: Detailed sccache diagnostics
shell: bash
run: |
# Enable verbose logging
export RUST_LOG=sccache=debug
export SCCACHE_LOG=debug
# Show detailed stats
sccache --show-stats --stats-format json > sccache-stats.json
# Check what's being hashed
echo "Checking hash inputs..."
env | grep -E "^(CARGO_|RUST|SCCACHE_)" | sort
```
## The Consistent Hash Mystery
The hash `8a6df4cb573bcfbf8c8e0bad63dc2287eedbfcc340fd86fbbdfa3b71d52e1c25` is likely:
1. **A namespace identifier** derived from the repository or workflow context
2. **Not changing between builds** because it's based on stable inputs
3. **Not the compilation hash** but rather the cache storage prefix
This suggests the cache backend IS working, but cache entries aren't being matched due to key mismatches.
## Immediate Action Items
1. **Add cache key configuration** (SCCACHE_GHA_CACHE_TO/FROM)
2. **Enable debug logging** to see why cache misses occur
3. **Consider using Swatinem/rust-cache** alongside sccache for dependency caching
4. **Test with a simple library crate** to verify sccache works at all
## Alternative: Rely More on Swatinem/rust-cache
The workflow already uses `Swatinem/rust-cache` (line 111-117) which handles:
- Cargo registry caching
- Target directory caching
- Proper cache key generation
Consider removing sccache temporarily and optimizing Swatinem/rust-cache configuration instead, as it's specifically designed for Rust and handles GitHub Actions caching properly.
## Conclusion
The 0% cache hit rate is most likely due to missing `SCCACHE_GHA_CACHE_TO` and `SCCACHE_GHA_CACHE_FROM` configuration, causing sccache to use dynamic cache keys that never match between runs. The consistent hash in the output is a red herring - it's a namespace identifier, not the cache key being used for compilation artifacts.
+50 -59
View File
@@ -1139,7 +1139,10 @@ fn exec_render_parts_generic(
) -> (Vec<Line<'static>>, Vec<Line<'static>>) {
let mut pre: Vec<Line<'static>> = Vec::new();
let command_escaped = strip_bash_lc_and_escape(command);
let mut cmd_lines = command_escaped.lines();
// Highlight the full command as a bash snippet; we will append
// the running duration (when applicable) to the first visual line.
let mut highlighted_cmd: Vec<Line<'static>> =
crate::syntax_highlight::highlight_code_block(&command_escaped, Some("bash"));
let header_line = match output {
None => {
@@ -1170,30 +1173,18 @@ fn exec_render_parts_generic(
),
};
if let Some(first) = cmd_lines.next() {
let duration_str = if output.is_none() && start_time.is_some() {
pre.push(header_line.clone());
if let Some(first) = highlighted_cmd.first_mut() {
// Append duration (dim) to the first highlighted line when running
if output.is_none() && start_time.is_some() {
let elapsed = start_time.unwrap().elapsed();
format!(" ({})", format_duration(elapsed))
} else {
String::new()
};
pre.push(header_line.clone());
pre.push(Line::from(vec![
Span::styled(
first.to_string(),
Style::default().fg(crate::colors::text()),
),
duration_str.dim(),
]));
} else {
pre.push(header_line);
}
for cont in cmd_lines {
pre.push(Line::styled(
cont.to_string(),
Style::default().fg(crate::colors::text()),
));
let duration_str = format!(" ({})", format_duration(elapsed));
first
.spans
.push(Span::styled(duration_str, Style::default().fg(crate::colors::text_dim())));
}
}
pre.extend(highlighted_cmd);
// Output: always include for generic exec
let out = output_lines(output, false, false);
@@ -1503,10 +1494,20 @@ fn exec_render_parts_parsed(
));
}
_ => {
spans.push(Span::styled(
line_text.to_string(),
Style::default().fg(crate::colors::text()),
));
// Apply shell syntax highlighting to executed command lines.
// We highlight the single logical line as bash and append its spans inline.
let mut hl = crate::syntax_highlight::highlight_code_block(line_text, Some("bash"));
if let Some(mut first) = hl.pop() {
// `highlight_code_block` returns exactly one line for single-line input.
// Append the highlighted spans inline after the prefix.
spans.extend(first.spans.drain(..));
} else {
// Fallback: plain text if highlighting yields nothing.
spans.push(Span::styled(
line_text.to_string(),
Style::default().fg(crate::colors::text()),
));
}
}
}
pre.push(Line::from(spans));
@@ -3736,12 +3737,16 @@ fn new_parsed_command(
));
}
_ => {
// For executed commands (Run/Test/Lint/etc.), show the command text
// in normal text color rather than dimmed.
spans.push(Span::styled(
line_text.to_string(),
Style::default().fg(crate::colors::text()),
));
// For executed commands (Run/Test/Lint/etc.), use shell syntax highlighting.
let mut hl = crate::syntax_highlight::highlight_code_block(line_text, Some("bash"));
if let Some(mut first) = hl.pop() {
spans.extend(first.spans.drain(..));
} else {
spans.push(Span::styled(
line_text.to_string(),
Style::default().fg(crate::colors::text()),
));
}
}
}
@@ -3765,7 +3770,9 @@ fn new_exec_command_generic(
) -> Vec<Line<'static>> {
let mut lines: Vec<Line<'static>> = Vec::new();
let command_escaped = strip_bash_lc_and_escape(command);
let mut cmd_lines = command_escaped.lines();
// Highlight the command as bash and then append a dimmed duration to the
// first visual line while running.
let mut highlighted_cmd = crate::syntax_highlight::highlight_code_block(&command_escaped, Some("bash"));
let header_line = match output {
None => {
@@ -3800,33 +3807,17 @@ fn new_exec_command_generic(
}
};
if let Some(first) = cmd_lines.next() {
let duration_str = if output.is_none() && start_time.is_some() {
lines.push(header_line.clone());
if let Some(first) = highlighted_cmd.first_mut() {
if output.is_none() && start_time.is_some() {
let elapsed = start_time.unwrap().elapsed();
format!(" ({})", format_duration(elapsed))
} else {
String::new()
};
lines.push(header_line.clone());
// Show the command itself in standard text color; keep the duration dimmed
lines.push(Line::from(vec![
Span::styled(
first.to_string(),
Style::default().fg(crate::colors::text()),
),
duration_str.dim(),
]));
} else {
lines.push(header_line);
}
for cont in cmd_lines {
lines.push(Line::styled(
cont.to_string(),
Style::default().fg(crate::colors::text()),
));
let duration_str = format!(" ({})", format_duration(elapsed));
first
.spans
.push(Span::styled(duration_str, Style::default().fg(crate::colors::text_dim())));
}
}
lines.extend(highlighted_cmd);
lines.extend(output_lines(output, false, true));
lines
-103
View File
@@ -1,103 +0,0 @@
# Fix for CARGO_PKG_VERSION Not Being Set During Releases
## Problem
The `CARGO_PKG_VERSION` environment variable was always showing "0.0.0" in release builds because:
1. The workspace `Cargo.toml` had `version = "0.0.0"` hardcoded
2. All packages inherited this version with `version = { workspace = true }`
3. The release process updated the version only in a tag/branch, not in the actual build environment
This caused issues with:
- Version checks in the application
- Update notifications showing incorrect versions
- MCP server version reporting
## Solution
The fix involves updating the GitHub Actions workflow to dynamically set the version from the release tag during the build process.
### Changes Made
#### 1. Updated GitHub Actions Workflow (`.github/workflows/rust-release.yml`)
The workflow now:
- Extracts the version from the tag name (e.g., `rust-v0.21.0` → `0.21.0`)
- Updates the `version` field in `codex-rs/Cargo.toml` before building
- Ensures all binaries are built with the correct version embedded
Key changes:
- Modified `tag-check` job to output the version
- Added "Update version in Cargo.toml" step in the `build` job
- Version is updated using `sed` before compilation
#### 2. Simplified Release Script (`codex-rs/scripts/create_github_release.sh`)
The script now:
- Only creates and pushes a tag (no branch or commit needed)
- Validates the version format
- Provides clear feedback about what will happen
- The actual version update happens in GitHub Actions
### How It Works
1. **Developer runs release script:**
```bash
./codex-rs/scripts/create_github_release.sh 0.21.0
```
2. **Script creates and pushes tag:**
- Validates version format
- Creates tag `rust-v0.21.0`
- Pushes to GitHub
3. **GitHub Actions workflow triggers:**
- Extracts version from tag (`0.21.0`)
- Updates `Cargo.toml`: `version = "0.21.0"`
- Builds all binaries with correct version
- Creates GitHub release with artifacts
4. **Binary contains correct version:**
- `env!("CARGO_PKG_VERSION")` returns `"0.21.0"`
- Version checks work correctly
- Update notifications show proper versions
### Testing
To test the version embedding locally:
```bash
# Run the test script
./test-version-embedding.sh
```
This script simulates what the GitHub Actions workflow does and verifies that `CARGO_PKG_VERSION` gets set correctly.
### Benefits
1. **No manual version updates needed** - Version is derived from the tag
2. **Single source of truth** - The tag name determines the version
3. **Consistent versioning** - All binaries in a release have the same version
4. **Simpler release process** - No need to commit version changes
### Important Notes
- The workspace `Cargo.toml` remains at `version = "0.0.0"` in the main branch
- The version is only updated during the release build process
- Local development builds will show "0.0.0" (this is expected)
- Release binaries will show the correct version from the tag
### Rollback
If you need to revert these changes:
1. Restore the original `create_github_release.sh` that updates and commits the version
2. Modify the workflow to expect the version to already be in `Cargo.toml`
3. Remove the "Update version in Cargo.toml" step from the workflow
### Future Improvements
Consider:
- Using a version management tool like `cargo-release`
- Implementing version bumping based on conventional commits
- Adding version validation to prevent accidental downgrades