mirror of
https://github.com/garrytan/gstack.git
synced 2026-10-31 14:06:50 +01:00
fix: /freeze, /guard, /unfreeze and /investigate work on Codex and other env-var hosts
Templates quoted "$HOME/.claude/skills/gstack/...", which the env-var hosts'
rewrites turned into "$HOME/.agents/skills/gstack/..." on Codex (a path a
global install never has; same class on the other hosts), and setup's
runtime roots had no freeze/bin. Quoted runtime paths now use
{{RUNTIME_ROOT}}: $GSTACK_ROOT on env-var hosts (the fence gets the
runtime prelude), the unchanged $HOME path on Claude. _link_runtime_dists
links freeze/bin and the careful/bin helper it sources into every
env-var host's root. Also fixes the same guess in /benchmark-models,
/retro and /diagram. test/env-host-runtime-assets.test.ts fails on the
previous code: no fence guesses a $HOME/<dir>/skills/gstack path, every
$GSTACK_ROOT path a fence uses is in the host's runtime root, and the
installed Codex /freeze and /unfreeze fences run from a fresh shell.
Template-reading tests now read the Claude render or expect the
placeholder (deliberate change).
This commit is contained in:
1 parent
c03236e40d
commit
5d2e6e76fb
16 files changed
+155
-20
No files matched your search
@@ -37,7 +37,7 @@ Different from `/benchmark` — that skill measures web page performance (Core W
|
||||
## Step 0: Locate the binary
|
||||
|
||||
```bash
|
||||
BIN="$HOME/.claude/skills/gstack/bin/gstack-model-benchmark"
|
||||
BIN="{{RUNTIME_ROOT}}/bin/gstack-model-benchmark"
|
||||
[ -x "$BIN" ] || BIN=".claude/skills/gstack/bin/gstack-model-benchmark"
|
||||
[ -x "$BIN" ] || { echo "ERROR: gstack-model-benchmark not found. Run ./setup in the gstack install dir." >&2; exit 1; }
|
||||
echo "BIN: $BIN"
|
||||
|
||||
@@ -73,7 +73,7 @@ gstack versions never clobber each other.
|
||||
|
||||
```bash
|
||||
BUNDLE=""
|
||||
for c in "$HOME/.claude/skills/gstack/lib/diagram-render/dist/diagram-render.html" \
|
||||
for c in "{{RUNTIME_ROOT}}/lib/diagram-render/dist/diagram-render.html" \
|
||||
"$(git rev-parse --show-toplevel 2>/dev/null)/lib/diagram-render/dist/diagram-render.html"; do
|
||||
[ -f "$c" ] && BUNDLE="$c" && break
|
||||
done
|
||||
|
||||
@@ -52,7 +52,7 @@ Once the user provides a directory path:
|
||||
|
||||
Set the user-selected boundary with the shared state writer. It resolves the physical absolute path and serializes replacement with investigation cleanup:
|
||||
```bash
|
||||
bash "$HOME/.claude/skills/gstack/freeze/bin/freeze-state.sh" set "<user-provided-path>"
|
||||
bash "{{RUNTIME_ROOT}}/freeze/bin/freeze-state.sh" set "<user-provided-path>"
|
||||
```
|
||||
|
||||
Only report success if the helper succeeds. On `FREEZE_BUSY` or unexpected state, preserve it and ask the user to inspect recovery after any active writer finishes; never write or delete the state file directly.
|
||||
|
||||
+1
-1
@@ -61,7 +61,7 @@ Once the user provides a directory path:
|
||||
|
||||
Set the user-selected boundary with the shared writer, which resolves a physical absolute path and serializes replacement with investigation cleanup:
|
||||
```bash
|
||||
bash "$HOME/.claude/skills/gstack/freeze/bin/freeze-state.sh" set "<user-provided-path>"
|
||||
bash "{{RUNTIME_ROOT}}/freeze/bin/freeze-state.sh" set "<user-provided-path>"
|
||||
```
|
||||
|
||||
On helper failure, do not claim the boundary is active. Preserve the state and report recovery; never bypass the shared writer with a direct write or deletion.
|
||||
|
||||
+1
-1
@@ -42,7 +42,7 @@ const codex = defineHost({
|
||||
// Mirrors create_codex_runtime_root in setup. design/dist and make-pdf/dist
|
||||
// back $GSTACK_DESIGN and $GSTACK_MAKE_PDF (#2891).
|
||||
runtimeRoot: {
|
||||
globalSymlinks: ['bin', 'lib', 'browse/dist', 'browse/bin', 'design/dist', 'make-pdf/dist', 'gstack-upgrade', 'ETHOS.md'],
|
||||
globalSymlinks: ['bin', 'lib', 'browse/dist', 'browse/bin', 'design/dist', 'make-pdf/dist', 'freeze/bin', 'careful/bin', 'gstack-upgrade', 'ETHOS.md'],
|
||||
globalFiles: {
|
||||
'review': ['checklist.md', 'design-checklist.md', 'greptile-triage.md', 'TODOS-format.md'],
|
||||
},
|
||||
|
||||
@@ -126,7 +126,7 @@ export function defineHost<const N extends string>(overrides: HostOverrides<N>):
|
||||
toolRewrites,
|
||||
suppressedResolvers = [...GBRAIN_RESOLVERS],
|
||||
runtimeRoot = {
|
||||
globalSymlinks: ['bin', 'lib', 'browse/dist', 'browse/bin', 'design/dist', 'make-pdf/dist', 'gstack-upgrade', 'ETHOS.md'],
|
||||
globalSymlinks: ['bin', 'lib', 'browse/dist', 'browse/bin', 'design/dist', 'make-pdf/dist', 'freeze/bin', 'careful/bin', 'gstack-upgrade', 'ETHOS.md'],
|
||||
globalFiles: {
|
||||
'review': ['checklist.md', 'TODOS-format.md'],
|
||||
},
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ const opencode = defineHost({
|
||||
// OpenCode links a wider runtime asset set than the shared default
|
||||
// (design binary, review specialists, qa templates/references, DX hall of fame).
|
||||
runtimeRoot: {
|
||||
globalSymlinks: ['bin', 'lib', 'browse/dist', 'browse/bin', 'design/dist', 'make-pdf/dist', 'gstack-upgrade', 'ETHOS.md', 'review/specialists', 'qa/templates', 'qa/references', 'plan-devex-review/dx-hall-of-fame.md'],
|
||||
globalSymlinks: ['bin', 'lib', 'browse/dist', 'browse/bin', 'design/dist', 'make-pdf/dist', 'freeze/bin', 'careful/bin', 'gstack-upgrade', 'ETHOS.md', 'review/specialists', 'qa/templates', 'qa/references', 'plan-devex-review/dx-hall-of-fame.md'],
|
||||
globalFiles: {
|
||||
'review': ['checklist.md', 'design-checklist.md', 'greptile-triage.md', 'TODOS-format.md'],
|
||||
},
|
||||
|
||||
@@ -121,14 +121,14 @@ After forming your root cause hypothesis, lock edits to the affected module to p
|
||||
```bash
|
||||
# $HOME-anchored like the careful/freeze frontmatter hooks: early skill bash runs
|
||||
# before CLAUDE_SKILL_DIR exists, so a relative path would never resolve.
|
||||
_FREEZE_SCRIPT="$HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"
|
||||
_FREEZE_SCRIPT="{{RUNTIME_ROOT}}/freeze/bin/check-freeze.sh"
|
||||
[ -x "$_FREEZE_SCRIPT" ] && echo "FREEZE_AVAILABLE" || echo "FREEZE_UNAVAILABLE"
|
||||
```
|
||||
|
||||
**If FREEZE_AVAILABLE:** Identify the narrowest directory containing the affected files. Acquire a run-owned boundary; the helper resolves its physical absolute path and leaves any pre-existing user or other-run boundary untouched:
|
||||
|
||||
```bash
|
||||
bash "$HOME/.claude/skills/gstack/freeze/bin/freeze-state.sh" acquire "<detected-directory>"
|
||||
bash "{{RUNTIME_ROOT}}/freeze/bin/freeze-state.sh" acquire "<detected-directory>"
|
||||
```
|
||||
|
||||
Substitute `<detected-directory>` with the actual path (e.g., `src/auth/`). Retain the exact returned `FREEZE_OWNER` token in this run's context (including any checkpoint); never reconstruct it from the current state file. Only a returned token means this run owns a new lock. `FREEZE_PRESERVED` means keep the existing boundary and do not clean it up. On acquisition error, pause before edits and report it; never claim a lock was acquired. Relative legacy state is ambiguous: ask the user to re-establish an absolute boundary via `/freeze`, rather than guessing its original cwd.
|
||||
@@ -138,7 +138,7 @@ Tell the user the boundary and its owner disposition. Hooks enforce Edit/Write r
|
||||
**Terminal cleanup:** On completion, explicit abort, or any known error that ends this investigation, run the following with this run's retained token, before the final response. Skip it when this run acquired no token:
|
||||
|
||||
```bash
|
||||
bash "$HOME/.claude/skills/gstack/freeze/bin/freeze-state.sh" release "<retained-owner-token>"
|
||||
bash "{{RUNTIME_ROOT}}/freeze/bin/freeze-state.sh" release "<retained-owner-token>"
|
||||
```
|
||||
|
||||
The helper compares ownership and removes state under the same mutation lock used by `/freeze`, `/guard`, and `/unfreeze`; a replacement boundary is preserved, even at the same path. Report cleanup errors or `FREEZE_PRESERVED`, never retry by deleting state directly. A hard-killed session cannot run this cleanup: recovery is explicit `/unfreeze` (user-requested removal) or `/freeze` (user-selected replacement). If a mutation lock was abandoned, inspect it with the user after confirming no writer is active; never automatically delete an ambiguous lock.
|
||||
|
||||
+1
-1
@@ -106,7 +106,7 @@ Remember whether the fetch succeeded — the stale-base guard in Step 1 only BLO
|
||||
Run `gstack-retro-metrics` with the detected base branch and computed start:
|
||||
|
||||
```bash
|
||||
_RM="$HOME/.claude/skills/gstack/bin/gstack-retro-metrics"
|
||||
_RM="{{RUNTIME_ROOT}}/bin/gstack-retro-metrics"
|
||||
[ -x "$_RM" ] || _RM=".claude/skills/gstack/bin/gstack-retro-metrics"
|
||||
"$_RM" --base "<default>" --since "<since>" \
|
||||
|| echo "RETRO_METRICS: unavailable — stale install (read the helper source for manual computation)"
|
||||
|
||||
@@ -140,6 +140,10 @@ export const RESOLVERS: Record<string, ResolverFn> = {
|
||||
DX_FRAMEWORK: generateDxFramework,
|
||||
TASTE_PROFILE: generateTasteProfile,
|
||||
BIN_DIR: (ctx) => ctx.paths.binDir,
|
||||
// Quotable runtime root for paths under gstack's install (freeze/bin, bin, lib):
|
||||
// $GSTACK_ROOT on env-var hosts (each fence gets the runtime prelude), the
|
||||
// literal $HOME path on Claude, so a quoted path keeps working with spaces.
|
||||
RUNTIME_ROOT: (ctx) => getHostConfig(ctx.host).usesEnvVars ? '$GSTACK_ROOT' : `$HOME/${getHostConfig(ctx.host).globalRoot}`,
|
||||
// Literal bin dir for skills that skip the shared preamble, where
|
||||
// $GSTACK_BIN is never set (#2906): the per-install root when rendering for
|
||||
// one, else the host's default global root.
|
||||
|
||||
@@ -621,15 +621,18 @@ _link_or_copy() {
|
||||
}
|
||||
|
||||
# _link_runtime_dists SOURCE ROOT — the compiled design and make-pdf binaries
|
||||
# behind $GSTACK_DESIGN and $GSTACK_MAKE_PDF on every env-var host (#2891).
|
||||
# dist/ only, never the whole directory: make-pdf/ ships a Claude SKILL.md a
|
||||
# host scanning its skills root would discover as a duplicate skill.
|
||||
# behind $GSTACK_DESIGN and $GSTACK_MAKE_PDF on every env-var host (#2891),
|
||||
# and freeze/bin (with the careful/bin helper it sources), whose state and
|
||||
# check scripts /freeze, /guard, /unfreeze and /investigate run as
|
||||
# "$GSTACK_ROOT/freeze/bin/...". Subdirectories only, never the whole skill
|
||||
# directory: make-pdf/, freeze/ and careful/ ship a Claude SKILL.md a host
|
||||
# scanning its skills root would discover as a duplicate skill.
|
||||
_link_runtime_dists() {
|
||||
local d
|
||||
for d in design/dist make-pdf/dist; do
|
||||
for d in design/dist make-pdf/dist freeze/bin careful/bin; do
|
||||
[ -d "$1/$d" ] || continue
|
||||
if [ "$IS_WINDOWS" -eq 1 ] || [ -L "$2/$d" ] || [ ! -e "$2/$d" ]; then
|
||||
mkdir -p "$2/${d%/dist}"
|
||||
mkdir -p "$2/${d%/*}"
|
||||
_link_or_copy "$1/$d" "$2/$d"
|
||||
fi
|
||||
done
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* Env-var hosts reach gstack's runtime files only through their installed
|
||||
* runtime root (found in Codex integration, lane W, 2026-10-04).
|
||||
*
|
||||
* Templates that quoted "$HOME/.claude/skills/gstack/..." were rewritten to
|
||||
* "$HOME/.agents/skills/gstack/..." on Codex, a directory a global install
|
||||
* does not have, and the runtime roots setup builds had no freeze/bin, so
|
||||
* /freeze, /guard, /unfreeze and /investigate's edit boundary failed on every
|
||||
* env-var host. Fences now use $GSTACK_ROOT (with the runtime prelude) and
|
||||
* setup links freeze/bin plus the careful/bin helper it sources.
|
||||
*/
|
||||
import { afterAll, beforeAll, describe, expect, test } from 'bun:test';
|
||||
import { spawnSync } from 'child_process';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import { ALL_HOST_CONFIGS } from '../hosts';
|
||||
import { runGeneration } from '../scripts/gen-skill-docs';
|
||||
|
||||
const ROOT = path.resolve(import.meta.dir, '..');
|
||||
const SETUP_SRC = fs.readFileSync(path.join(ROOT, 'setup'), 'utf-8');
|
||||
const ENV_HOSTS = ALL_HOST_CONFIGS.filter(h => h.usesEnvVars);
|
||||
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-runtime-assets-'));
|
||||
const renderDir = path.join(tmp, 'render');
|
||||
|
||||
// Lookups that probe Claude-layout installs on purpose and fall through when
|
||||
// absent: /gstack-upgrade's install-type detection and the optional Chrome
|
||||
// extension path. design-html's vendored pretext.js is an optional probe too.
|
||||
const PROBE_FILES = /\/gstack-(?:upgrade|open-gstack-browser|connect-chrome)\/SKILL\.md$/;
|
||||
const OPTIONAL_ROOT_PATHS = ['design-html/vendor'];
|
||||
|
||||
function walk(dir: string): string[] {
|
||||
return fs.readdirSync(dir, { withFileTypes: true }).flatMap(e =>
|
||||
e.isDirectory() ? walk(path.join(dir, e.name)) : e.name.endsWith('.md') ? [path.join(dir, e.name)] : []);
|
||||
}
|
||||
const fences = (text: string) => [...text.matchAll(/```bash\n([\s\S]*?)\n```/g)].map(m => m[1]!);
|
||||
|
||||
function extractFunction(name: string): string {
|
||||
const start = SETUP_SRC.indexOf(`\n${name}() {`);
|
||||
const end = SETUP_SRC.indexOf('\n}\n', start);
|
||||
if (start < 0 || end < 0) throw new Error(`Could not locate ${name}() in setup`);
|
||||
return SETUP_SRC.slice(start + 1, end + 2);
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
const result = await runGeneration({ host: 'all', outputRoot: renderDir });
|
||||
if (result.exitCode !== 0) throw new Error(result.diagnostics.filter(d => d.kind === 'error').map(d => d.message).join('\n'));
|
||||
});
|
||||
afterAll(() => fs.rmSync(tmp, { recursive: true, force: true }));
|
||||
|
||||
describe('env-var host fences reach runtime files through their runtime root', () => {
|
||||
test('no fence guesses a $HOME/<dir>/skills/gstack path', () => {
|
||||
const problems: string[] = [];
|
||||
for (const host of ENV_HOSTS) {
|
||||
const base = path.join(renderDir, host.hostSubdir, 'skills');
|
||||
for (const file of walk(base).filter(f => !PROBE_FILES.test(f))) {
|
||||
for (const body of fences(fs.readFileSync(file, 'utf8'))) {
|
||||
for (const m of body.matchAll(/\$(?:HOME|\{HOME\})\/\.[A-Za-z0-9._-]+(?:\/[A-Za-z0-9._-]+)*\/skills\/gstack\/[^\s"']*/g)) {
|
||||
problems.push(`${host.name}: ${path.relative(renderDir, file)}: ${m[0]}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
expect(problems.slice(0, 20)).toEqual([]);
|
||||
});
|
||||
|
||||
test("every $GSTACK_ROOT path a fence uses is in the host's runtime root", () => {
|
||||
const problems: string[] = [];
|
||||
for (const host of ENV_HOSTS) {
|
||||
const provided = [...host.runtimeRoot.globalSymlinks,
|
||||
...Object.entries(host.runtimeRoot.globalFiles ?? {}).flatMap(([dir, files]) => files.map(f => `${dir}/${f}`))];
|
||||
const base = path.join(renderDir, host.hostSubdir, 'skills');
|
||||
for (const file of walk(base)) {
|
||||
for (const body of fences(fs.readFileSync(file, 'utf8'))) {
|
||||
for (const m of body.matchAll(/\$\{?GSTACK_ROOT\}?\/([A-Za-z0-9._-]+(?:\/[A-Za-z0-9._-]+)*)/g)) {
|
||||
const rel = m[1]!;
|
||||
const covered = [...provided, ...OPTIONAL_ROOT_PATHS].some(p => rel === p || rel.startsWith(`${p}/`));
|
||||
if (!covered) problems.push(`${host.name}: ${path.relative(renderDir, file)}: $GSTACK_ROOT/${rel}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
expect([...new Set(problems)].slice(0, 20)).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe.skipIf(process.platform === 'win32')('Codex global install: the freeze boundary works from a fresh shell', () => {
|
||||
test('setup links freeze/bin and careful/bin, and /freeze then /unfreeze run as installed', () => {
|
||||
const sandbox = fs.mkdtempSync(path.join(os.tmpdir(), 'gstack-codex-freeze-'));
|
||||
try {
|
||||
const home = path.join(sandbox, 'home');
|
||||
const codexHome = path.join(home, '.codex');
|
||||
const rootDir = path.join(codexHome, 'skills', 'gstack');
|
||||
const project = path.join(sandbox, 'project', 'src');
|
||||
fs.mkdirSync(project, { recursive: true });
|
||||
const functions = ['_link_or_copy', '_link_runtime_dists', '_copy_skill_md', '_gstack_generated_header', 'create_codex_runtime_root']
|
||||
.filter(name => SETUP_SRC.includes(`\n${name}() {`)).map(extractFunction).join('\n');
|
||||
const built = spawnSync('bash', ['-c', `IS_WINDOWS=0\n_CODEX_RENDER_ROOT="${renderDir}"\n${functions}\ncreate_codex_runtime_root "${ROOT}" "${rootDir}"`],
|
||||
{ encoding: 'utf8', timeout: 30_000, env: { PATH: process.env.PATH ?? '/usr/bin:/bin', HOME: home } });
|
||||
expect(built.stderr).toBe('');
|
||||
expect(built.status).toBe(0);
|
||||
for (const rel of ['freeze/bin/freeze-state.sh', 'freeze/bin/check-freeze.sh', 'careful/bin/hook-extract.sh']) {
|
||||
expect(fs.existsSync(path.join(rootDir, rel)), rel).toBe(true);
|
||||
}
|
||||
expect(fs.existsSync(path.join(rootDir, 'freeze', 'SKILL.md'))).toBe(false);
|
||||
|
||||
const skill = (name: string) => fs.readFileSync(path.join(renderDir, '.agents', 'skills', name, 'SKILL.md'), 'utf8');
|
||||
const fence = (name: string, marker: string) => fences(skill(name)).find(body => body.includes(marker))!;
|
||||
const run = (script: string) => spawnSync('bash', ['-c', script], {
|
||||
cwd: path.dirname(project), encoding: 'utf8', timeout: 30_000,
|
||||
env: { PATH: process.env.PATH ?? '/usr/bin:/bin', HOME: home, CODEX_HOME: codexHome },
|
||||
});
|
||||
const set = run(fence('gstack-freeze', 'freeze-state.sh" set').replace('<user-provided-path>', project));
|
||||
expect(set.stderr).toBe('');
|
||||
expect(set.status).toBe(0);
|
||||
expect(set.stdout).toContain(`FREEZE_DIR=${fs.realpathSync(project)}`);
|
||||
const clear = run(fence('gstack-unfreeze', 'freeze-state.sh" clear'));
|
||||
expect(clear.status, clear.stderr).toBe(0);
|
||||
} finally {
|
||||
fs.rmSync(sandbox, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -5,7 +5,8 @@ import { join, resolve, dirname } from 'node:path';
|
||||
import { spawn, spawnSync } from 'node:child_process';
|
||||
|
||||
const ROOT = resolve(import.meta.dir, '..');
|
||||
const template = readFileSync(join(ROOT, 'investigate/SKILL.md.tmpl'), 'utf8');
|
||||
// The Claude render: the template names the root as {{RUNTIME_ROOT}} ($HOME/.claude/skills/gstack on Claude).
|
||||
const template = readFileSync(join(ROOT, 'investigate/SKILL.md'), 'utf8');
|
||||
const scope = template.split('## Scope Lock')[1].split('\n---')[0];
|
||||
const acquisition = [...scope.matchAll(/```bash\n([\s\S]*?)```/g)][1][1];
|
||||
const registered = template.match(/command: '(.*check-freeze\.sh.*)'/)![1].replace(/''/g, "'");
|
||||
|
||||
@@ -173,7 +173,8 @@ describe('gstack-retro-metrics contract', () => {
|
||||
|
||||
test('the skill fence invokes the script with primary path + degraded fallback', () => {
|
||||
const tmpl = fs.readFileSync(path.join(ROOT, 'retro', 'SKILL.md.tmpl'), 'utf-8');
|
||||
expect(tmpl).toContain('$HOME/.claude/skills/gstack/bin/gstack-retro-metrics');
|
||||
expect(tmpl).toContain('"{{RUNTIME_ROOT}}/bin/gstack-retro-metrics"');
|
||||
expect(fs.readFileSync(path.join(ROOT, 'retro', 'SKILL.md'), 'utf-8')).toContain('$HOME/.claude/skills/gstack/bin/gstack-retro-metrics');
|
||||
expect(tmpl).toContain('".claude/skills/gstack/bin/gstack-retro-metrics"');
|
||||
expect(tmpl).toContain('--base "<default>" --since "<since>"');
|
||||
expect(tmpl).toContain(
|
||||
|
||||
@@ -29,7 +29,10 @@ describe('investigate freeze path resolution', () => {
|
||||
});
|
||||
|
||||
test(`${rel} scope lock availability probe uses the $HOME anchor`, () => {
|
||||
expect(content).toContain('_FREEZE_SCRIPT="$HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"');
|
||||
// The template names the host's runtime root; Claude's render keeps the $HOME anchor.
|
||||
expect(content).toContain(rel.endsWith('.tmpl')
|
||||
? '_FREEZE_SCRIPT="{{RUNTIME_ROOT}}/freeze/bin/check-freeze.sh"'
|
||||
: '_FREEZE_SCRIPT="$HOME/.claude/skills/gstack/freeze/bin/check-freeze.sh"');
|
||||
expect(content).toContain('[ -x "$_FREEZE_SCRIPT" ] && echo "FREEZE_AVAILABLE" || echo "FREEZE_UNAVAILABLE"');
|
||||
});
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ echo '{"skill":"unfreeze","ts":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","repo":"'$(bas
|
||||
## Clear the boundary
|
||||
|
||||
```bash
|
||||
bash "$HOME/.claude/skills/gstack/freeze/bin/freeze-state.sh" clear
|
||||
bash "{{RUNTIME_ROOT}}/freeze/bin/freeze-state.sh" clear
|
||||
```
|
||||
|
||||
This is explicit user-requested removal, not investigation cleanup. The shared writer serializes it with acquisition, replacement and owner-checked release. On `FREEZE_BUSY` or unexpected state, leave everything untouched and report recovery; never delete state or a possibly active mutation lock directly.
|
||||
|
||||
Reference in new issue
Block a user