Tell a session where the other repository is

0af87a65f852 · AtlantisPleb · · parent 979ee0f928d5

Tell a session where the other repository is

A session was asked where three delegate lane strings were defined. They are in
the monorepo and it was standing in the web application, and it never found
them. What it did instead is the whole bug report:

    git grep -in "opencode/gemini" ../openagents
    → fatal: '../openagents' is outside repository at '.../openagents.com'

    grep -rn "gemini-3.7-flash" /Users/christopherdavid/work/
    → The command did not finish within 120s and was stopped.

It had the right path. `git grep` refuses a path outside the repository it is
run in, so the guess failed for a reason unrelated to the guess, and the
fallback was the workspace root — which holds a hundred read-only clones of
other people's repositories, so a recursive grep there does not finish. That
was the turn.

The standing context named both repositories and never said where either one
was on disk. It says now, when the other one is actually checked out beside
this one: the absolute path, that reaching it means changing directory first,
why `git grep` will not do it from here, and that the workspace root is not a
place to search.

Checked rather than assumed. A machine holding only one of the two is told
nothing about a sibling, because an instruction to `cd` somewhere that does not
exist is worse than no instruction.

Verified against the failing question: the same prompt now answers with the
file and both line numbers on the first search.

684 tests pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012TRDRrfL1khQhQtNr3SRrA
Co-Authored-By
Claude Opus 5 (1M context) <noreply@anthropic.com>

Deploy story

What this commit did to the running system — joined from the forge receipt chain, the part a commit page elsewhere cannot show.

Not deployed through the forge lane

No push, promotion, build, or deploy receipt references this commit (receipts are scanned over a bounded recent window). Changes shipped by full node replacement carry their proof in the release gate receipt instead.

Changed files

  • modified packages/openagents-cli/src/coder-skills.ts
  • modified packages/openagents-cli/test/coder-skills.test.ts

Diff

2 files changed, +104 -4

packages/openagents-cli/src/coder-skills.ts modified +48 -4

@@ -18,9 +18,9 @@

18 18
 * server's. A tool description reaches both.
19 19
 */
20 20
21
import { mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
21
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
22 22
import { homedir } from "node:os";
23
import { dirname, join } from "node:path";
23
import { basename, dirname, join } from "node:path";
24 24
import { fileURLToPath } from "node:url";
25 25
26 26
/**

@@ -325,7 +325,8 @@ export const standingContext = (

325 325
 */
326 326
const openAgentsWorkspace = (cwd: string): string | undefined => {
327 327
  if (!/openagents/i.test(cwd)) return undefined;
328
  return [
328
329
  const lines = [
329 330
    `This session is working in ${cwd}, which is part of OpenAgents. Two repositories carry`,
330 331
    "most of the work, and they are easy to confuse:",
331 332
    "",

@@ -337,5 +338,48 @@ const openAgentsWorkspace = (cwd: string): string | undefined => {

337 338
    "",
338 339
    "They are separate repositories with separate issue lists, so name the one you mean when you",
339 340
    "read or write issues, and do not assume the current directory is the one being asked about.",
340
  ].join("\n");
341
  ];
342
343
  // Where the other one is, when it is checked out beside this one.
344
  //
345
  // Naming the two repositories without saying where the other one lives sent
346
  // a session looking for a CLI constant in the wrong tree: it guessed the
347
  // sibling path correctly, ran `git grep <pattern> ../openagents` — which git
348
  // refuses, because the path is outside the repository it is standing in —
349
  // and then fell back to grepping the whole workspace root, which holds every
350
  // read-only reference clone and took the tool's whole 120-second budget
351
  // before being stopped.
352
  const sibling = siblingCheckout(cwd);
353
354
  if (sibling !== undefined) {
355
    lines.push(
356
      "",
357
      `The other one is checked out at \`${sibling.path}\`. To search or read it, change`,
358
      `directory first — \`cd ${sibling.path} && git grep …\`. \`git grep\` refuses a path`,
359
      "outside the repository it is run in, and it is the one command most likely to be reached",
360
      "for here.",
361
      "",
362
      `Do not search \`${dirname(sibling.path)}\` itself. It is the workspace root, and it holds`,
363
      "large read-only clones of other people's repositories; a recursive grep there does not",
364
      "finish. Search one repository at a time.",
365
    );
366
  }
367
368
  return lines.join("\n");
369
};
370
371
/**
372
 * The other OpenAgents repository, if it is checked out beside this one.
373
 *
374
 * Checked rather than assumed: a machine with only one of the two would
375
 * otherwise be told to `cd` somewhere that does not exist, which is a worse
376
 * instruction than none.
377
 */
378
const siblingCheckout = (cwd: string): { readonly name: string; readonly path: string } | undefined => {
379
  const here = basename(cwd);
380
  const other = here === "openagents.com" ? "openagents" : here === "openagents" ? "openagents.com" : undefined;
381
  if (other === undefined) return undefined;
382
383
  const path = join(dirname(cwd), other);
384
  return existsSync(join(path, ".git")) ? { name: other, path } : undefined;
341 385
};
packages/openagents-cli/test/coder-skills.test.ts modified +56

@@ -302,3 +302,59 @@ describe("what a session is told without asking", () => {

302 302
    expect(standingContext([], "/Users/x/work/something")).toBeUndefined();
303 303
  });
304 304
});
305
306
describe("finding the other OpenAgents repository", () => {
307
  const workspace = () => {
308
    const root = mkdtempSync(join(tmpdir(), "oa-work-"));
309
    for (const name of ["openagents", "openagents.com"]) {
310
      mkdirSync(join(root, name, ".git"), { recursive: true });
311
    }
312
    return root;
313
  };
314
315
  it("says where the sibling is, and how to reach it", () => {
316
    // The gap this closes: a session was told the two repositories exist and
317
    // not where the other one was. It guessed the path right, ran
318
    // `git grep <pattern> ../openagents`, and git refused it for being outside
319
    // the repository — then fell back to grepping the workspace root, which
320
    // holds every read-only reference clone, and spent the tool's whole
321
    // 120-second budget before being stopped.
322
    const root = workspace();
323
    const standing = standingContext([], join(root, "openagents.com")) ?? "";
324
325
    expect(standing).toContain(join(root, "openagents"));
326
    expect(standing).toContain("cd ");
327
    expect(standing).toContain("outside the repository");
328
  });
329
330
  it("warns off the workspace root by name", () => {
331
    const root = workspace();
332
    const standing = standingContext([], join(root, "openagents")) ?? "";
333
334
    expect(standing).toContain(join(root, "openagents.com"));
335
    expect(standing).toContain(`Do not search \`${root}\``);
336
  });
337
338
  it("says nothing about a sibling that is not checked out", () => {
339
    // Worse than no instruction: one telling the reader to `cd` somewhere that
340
    // does not exist.
341
    const root = mkdtempSync(join(tmpdir(), "oa-lone-"));
342
    mkdirSync(join(root, "openagents.com", ".git"), { recursive: true });
343
344
    const standing = standingContext([], join(root, "openagents.com")) ?? "";
345
346
    expect(standing).toContain("Two repositories carry");
347
    expect(standing).not.toContain("cd ");
348
    expect(standing).not.toContain("Do not search");
349
  });
350
351
  it("says nothing about a sibling for a directory that is neither", () => {
352
    const root = workspace();
353
    mkdirSync(join(root, "openagents-notes"), { recursive: true });
354
355
    const standing = standingContext([], join(root, "openagents-notes")) ?? "";
356
357
    expect(standing).toContain("Two repositories carry");
358
    expect(standing).not.toContain("cd ");
359
  });
360
});

This page updates live while a promote is in flight · changelog