Skip to content
Merged
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
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
# Changelog

## [0.11.9.0] - 2026-03-23 — Codex Skill Loading Fix

### Fixed

- **Codex no longer rejects gstack skills with "invalid SKILL.md".** Existing installs had oversized description fields (>1024 chars) that Codex silently rejected. The build now errors if any Codex description exceeds 1024 chars, setup always regenerates `.agents/` to prevent stale files, and a one-time migration auto-cleans oversized descriptions on existing installs.
- **`package.json` version now stays in sync with `VERSION`.** Was 6 minor versions behind. A new CI test catches future drift.

### Added

- **Codex E2E tests now assert no skill loading errors.** The exact "Skipped loading skill(s)" error that prompted this fix is now a regression test — `stderr` is captured and checked.
- **Codex troubleshooting entry in README.** Manual fix instructions for users who hit the loading error before the auto-migration runs.

### For contributors

- `test/gen-skill-docs.test.ts` validates all `.agents/` descriptions stay within 1024 chars
- `gstack-update-check` includes a one-time migration that deletes oversized Codex SKILL.md files
- P1 TODO added: Codex→Claude reverse buddy check skill

## [0.11.8.0] - 2026-03-23 — zsh Compatibility Fix

### Fixed
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,8 @@ Data is stored in [Supabase](https://supabase.com) (open source Firebase alterna

**Stale install?** Run `/gstack-upgrade` — or set `auto_upgrade: true` in `~/.gstack/config.yaml`

**Codex says "Skipped loading skill(s) due to invalid SKILL.md"?** Your Codex skill descriptions are stale. Fix: `cd ~/.codex/skills/gstack && git pull && ./setup --host codex` — or for repo-local installs: `cd "$(readlink -f .agents/skills/gstack)" && git pull && ./setup --host codex`

**Windows users:** gstack works on Windows 11 via Git Bash or WSL. Node.js is required in addition to Bun — Bun has a known bug with Playwright's pipe transport on Windows ([bun#4253](https://github.com/oven-sh/bun/issues/4253)). The browse server automatically falls back to Node.js. Make sure both `bun` and `node` are on your PATH.

**Claude says it can't see the skills?** Make sure your project's `CLAUDE.md` has a gstack section. Add this:
Expand Down
14 changes: 14 additions & 0 deletions TODOS.md
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,20 @@ Shipped in v0.8.3. Step 8.5 added to `/ship` — after creating the PR, `/ship`
**Depends on:** gstack-diff-scope (shipped)


## Codex

### Codex→Claude reverse buddy check skill

**What:** A Codex-native skill (`.agents/skills/gstack-claude/SKILL.md`) that runs `claude -p` to get an independent second opinion from Claude — the reverse of what `/codex` does today from Claude Code.

**Why:** Codex users deserve the same cross-model challenge that Claude users get via `/codex`. Currently the flow is one-way (Claude→Codex). Codex users have no way to get a Claude second opinion.

**Context:** The `/codex` skill template (`codex/SKILL.md.tmpl`) shows the pattern — it wraps `codex exec` with JSONL parsing, timeout handling, and structured output. The reverse skill would wrap `claude -p` with similar infrastructure. Would be generated into `.agents/skills/gstack-claude/` by `gen-skill-docs --host codex`.

**Effort:** M (human: ~2 weeks / CC: ~30 min)
**Priority:** P1
**Depends on:** None

## Completeness

### Completeness metrics dashboard
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.11.8.0
0.11.9.0
18 changes: 18 additions & 0 deletions bin/gstack-update-check
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,24 @@ if [ "$_UC" = "false" ]; then
exit 0
fi

# ─── Migration: fix stale Codex descriptions (one-time) ───────
# Existing installs may have .agents/skills/gstack/SKILL.md with oversized
# descriptions (>1024 chars) that Codex rejects. We can't regenerate from
# the runtime root (no bun/scripts), so delete oversized files — the next
# ./setup or /gstack-upgrade will regenerate them properly.
# Marker file ensures this runs at most once per install.
if [ ! -f "$STATE_DIR/.codex-desc-healed" ]; then
for _AGENTS_SKILL in "$GSTACK_DIR"/.agents/skills/*/SKILL.md; do
[ -f "$_AGENTS_SKILL" ] || continue
_DESC=$(awk '/^---$/{n++;next}n==1&&/^description:/{d=1;sub(/^description:\s*/,"");if(length>0)print;next}d&&/^ /{sub(/^ /,"");print;next}d{d=0}' "$_AGENTS_SKILL" | wc -c | tr -d ' ')
if [ "${_DESC:-0}" -gt 1024 ]; then
rm -f "$_AGENTS_SKILL"
fi
done
mkdir -p "$STATE_DIR"
touch "$STATE_DIR/.codex-desc-healed"
fi

# ─── Snooze helper ──────────────────────────────────────────
# check_snooze <remote_version>
# Returns 0 if snoozed (should stay quiet), 1 if not snoozed (should output).
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "gstack",
"version": "0.9.8.0",
"version": "0.11.9.0",
"description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.",
"license": "MIT",
"type": "module",
Expand Down
9 changes: 9 additions & 0 deletions scripts/gen-skill-docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2910,6 +2910,15 @@ function transformFrontmatter(content: string, host: Host): string {
const body = content.slice(fmEnd + 4); // includes the leading \n after ---
const { name, description } = extractNameAndDescription(content);

// Codex 1024-char description limit — fail build, don't ship broken skills
const MAX_DESC = 1024;
if (description.length > MAX_DESC) {
throw new Error(
`Codex description for "${name}" is ${description.length} chars (max ${MAX_DESC}). ` +
`Compress the description in the .tmpl file.`
);
}

// Re-emit Codex frontmatter (name + description only)
const indentedDesc = description.split('\n').map(l => ` ${l}`).join('\n');
const codexFm = `---\nname: ${name}\ndescription: |\n${indentedDesc}\n---`;
Expand Down
14 changes: 5 additions & 9 deletions setup
Original file line number Diff line number Diff line change
Expand Up @@ -128,17 +128,13 @@ if [ ! -x "$BROWSE_BIN" ]; then
exit 1
fi

# 1b. Generate .agents/ Codex skill docs if missing or stale
# 1b. Generate .agents/ Codex skill docs — always regenerate to prevent stale descriptions.
# .agents/ is no longer committed — generated at setup time from .tmpl templates.
# bun run build already does this, but we need it when NEEDS_BUILD=0 (binary is fresh
# but .agents/ hasn't been generated yet, e.g., fresh clone).
# bun run build already does this, but we need it when NEEDS_BUILD=0 (binary is fresh).
# Always regenerate: generation is fast (<2s) and mtime-based staleness checks are fragile
# (miss stale files when timestamps match after clone/checkout/upgrade).
AGENTS_DIR="$SOURCE_GSTACK_DIR/.agents/skills"
NEEDS_AGENTS_GEN=0
if [ ! -d "$AGENTS_DIR" ]; then
NEEDS_AGENTS_GEN=1
elif [ -n "$(find "$SOURCE_GSTACK_DIR" -maxdepth 2 -name 'SKILL.md.tmpl' -newer "$AGENTS_DIR" -print -quit 2>/dev/null)" ]; then
NEEDS_AGENTS_GEN=1
fi
NEEDS_AGENTS_GEN=1

if [ "$NEEDS_AGENTS_GEN" -eq 1 ] && [ "$NEEDS_BUILD" -eq 0 ]; then
echo "Generating .agents/ skill docs..."
Expand Down
3 changes: 3 additions & 0 deletions test/codex-e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,9 @@ describeCodex('Codex E2E', () => {

expect(result.exitCode).toBe(0);
expect(result.output.length).toBeGreaterThan(0);
// Skill loading errors mean our generated SKILL.md files are broken
expect(result.stderr).not.toContain('invalid');
expect(result.stderr).not.toContain('Skipped loading');
// The output should reference the skill name in some form
const outputLower = result.output.toLowerCase();
expect(
Expand Down
19 changes: 19 additions & 0 deletions test/gen-skill-docs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,25 @@ describe('gen-skill-docs', () => {
}
});

test(`every Codex SKILL.md description stays within ${MAX_SKILL_DESCRIPTION_LENGTH} chars`, () => {
const agentsDir = path.join(ROOT, '.agents', 'skills');
if (!fs.existsSync(agentsDir)) return; // skip if not generated
for (const entry of fs.readdirSync(agentsDir, { withFileTypes: true })) {
if (!entry.isDirectory()) continue;
const skillMd = path.join(agentsDir, entry.name, 'SKILL.md');
if (!fs.existsSync(skillMd)) continue;
const content = fs.readFileSync(skillMd, 'utf-8');
const description = extractDescription(content);
expect(description.length).toBeLessThanOrEqual(MAX_SKILL_DESCRIPTION_LENGTH);
}
});

test('package.json version matches VERSION file', () => {
const pkg = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf-8'));
const version = fs.readFileSync(path.join(ROOT, 'VERSION'), 'utf-8').trim();
expect(pkg.version).toBe(version);
});

test('generated files are fresh (match --dry-run)', () => {
const result = Bun.spawnSync(['bun', 'run', 'scripts/gen-skill-docs.ts', '--dry-run'], {
cwd: ROOT,
Expand Down
3 changes: 3 additions & 0 deletions test/helpers/codex-session-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export interface CodexResult {
durationMs: number; // Wall clock time
sessionId: string | null; // Thread ID for session continuity
rawLines: string[]; // Raw JSONL lines for debugging
stderr: string; // Stderr output (skill loading errors, auth failures)
}

// --- JSONL parser (ported from Python in codex/SKILL.md.tmpl) ---
Expand Down Expand Up @@ -167,6 +168,7 @@ export async function runCodexSkill(opts: {
durationMs: Date.now() - startTime,
sessionId: null,
rawLines: [],
stderr: '',
};
}

Expand Down Expand Up @@ -282,6 +284,7 @@ export async function runCodexSkill(opts: {
durationMs,
sessionId: parsed.sessionId,
rawLines: collectedLines,
stderr,
};
} finally {
// Clean up temp HOME
Expand Down