Compare commits

...
Author SHA1 Message Date
Time Attakc 39de1fd649 Merge branch 'master' into fix/backlog-c106 2026-08-01 05:15:54 +08:00
Time Attakc 894f1dd950 Merge branch 'master' into fix/backlog-c106 2026-07-28 00:26:01 -07:00
Garry TanandClaude Fable 5 f584246dab fix(skillpack): resolve gbrain root from module path when cwd walk fails (#1917)
findGbrainRoot only walked up from process.cwd(), so any bun global
install (markers at ~/.bun/install/global/node_modules/gbrain/) failed
with 'could not find gbrain repo root' unless the user happened to be
inside the install. When no explicit start is given and the cwd walk
misses, retry from bundle.ts's own location (import.meta.url) and from
dirname(process.argv[1]) — both resolve the bun-global layout and the
in-repo compiled binary. Explicit-start callers (tests) are unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 14:44:46 -07:00
2 changed files with 55 additions and 11 deletions
+38 -11
View File
@@ -9,6 +9,7 @@
import { existsSync, readFileSync, statSync, readdirSync } from 'fs';
import { join, dirname, isAbsolute, resolve } from 'path';
import { fileURLToPath } from 'url';
import { parseMarkdown } from '../markdown.ts';
@@ -38,19 +39,45 @@ export class BundleError extends Error {
/**
* Walk up from `start` (default cwd) looking for an `openclaw.plugin.json`
* sibling to `src/cli.ts`. That pair identifies a gbrain repo root.
*
* When no explicit `start` is given and the cwd walk fails (e.g. gbrain was
* installed globally via `bun install -g` and the user is in an unrelated
* directory, #1917), fall back to walking up from this module's own location
* and from the running entrypoint (`process.argv[1]`). Both resolve the
* bun-global layout (~/.bun/install/global/node_modules/gbrain/) and the
* in-repo compiled binary (bin/gbrain).
*/
export function findGbrainRoot(start: string = process.cwd()): string | null {
let dir = resolve(start);
for (let i = 0; i < 10; i++) {
if (
existsSync(join(dir, 'openclaw.plugin.json')) &&
existsSync(join(dir, 'src', 'cli.ts'))
) {
return dir;
export function findGbrainRoot(start?: string): string | null {
const walkUp = (from: string): string | null => {
let dir = resolve(from);
for (let i = 0; i < 10; i++) {
if (
existsSync(join(dir, 'openclaw.plugin.json')) &&
existsSync(join(dir, 'src', 'cli.ts'))
) {
return dir;
}
const parent = dirname(dir);
if (parent === dir) break;
dir = parent;
}
const parent = dirname(dir);
if (parent === dir) break;
dir = parent;
return null;
};
const found = walkUp(start ?? process.cwd());
if (found !== null || start !== undefined) return found;
const fallbacks: string[] = [];
try {
// Not a file:// URL inside a compiled binary; skip on error.
fallbacks.push(dirname(fileURLToPath(import.meta.url)));
} catch {
/* ignore */
}
if (process.argv[1]) fallbacks.push(dirname(resolve(process.argv[1])));
for (const candidate of fallbacks) {
const root = walkUp(candidate);
if (root !== null) return root;
}
return null;
}
+17
View File
@@ -125,6 +125,23 @@ describe('findGbrainRoot', () => {
it('returns null when no gbrain root above', () => {
expect(findGbrainRoot('/tmp/definitely-not-a-gbrain-repo-XYZ')).toBeNull();
});
it('falls back to the module location when cwd has no markers (#1917)', () => {
// Simulate a bun-global install: cwd is an unrelated directory with no
// gbrain markers anywhere above it. The no-arg call must still resolve
// via bundle.ts's own location (which lives in the real repo).
const elsewhere = mkdtempSync(join(tmpdir(), 'skillpack-elsewhere-'));
created.push(elsewhere);
const prevCwd = process.cwd();
try {
process.chdir(elsewhere);
const root = findGbrainRoot();
expect(root).not.toBeNull();
expect(existsSync(join(root!, 'openclaw.plugin.json'))).toBe(true);
expect(existsSync(join(root!, 'src', 'cli.ts'))).toBe(true);
} finally {
process.chdir(prevCwd);
}
});
});
describe('loadBundleManifest', () => {