Skip to content

repo: mode: unsafe dir default can reset an unrelated parent git repo; unconditional push on every call with no read-only mode #85

Description

@harshit-lyzr

query()'s repo: option has two related problems that make it unsafe to use from anything other than an interactive one-shot CLI session: (1) dir is typed as required but the runtime silently defaults to process.cwd(), which can resolve to an unrelated repo's working tree and get git reset --hard; (2) finalize() (commit + push) runs unconditionally at the end of every call, success or failure, with no way to opt out.

Bug 1 — LocalRepoOptions.dir type says required, runtime treats it as optional and defaults to process.cwd()
session.d.ts:

export interface LocalRepoOptions {
url: string;
token: string;
dir: string; // <- no ?, looks mandatory
session?: string;
}
sdk.js:

let dir = options.dir ?? process.cwd();
if (options.repo) {
...
localSession = initLocalSession({
url: options.repo.url,
token,
dir: options.repo.dir || dir, // <- silently falls back to cwd if unset
session: options.repo.session,
});
If the calling process's cwd has no .git of its own (e.g. it's a subdirectory like frontend/ inside a monorepo), git's normal directory-walk-up behavior means every command initLocalSession runs (checkout, reset --hard) executes against whatever parent repo git finds — which may have nothing to do with the repo.url being cloned.

Reproduction:

From a directory with no .git of its own, but nested inside a parent repo (e.g. some-monorepo/apps/web/), call:

query({ prompt: "hi", repo: { url: "https://github.com/org/some-other-repo", token } })
(no dir passed)
initLocalSession finds some-monorepo/apps/web already "exists" (it's a real directory), so it takes the update branch: git remote set-url origin , git fetch, git checkout , git reset --hard origin/ — all run with cwd: some-monorepo/apps/web.
Because that directory has no .git, these commands resolve to some-monorepo's actual .git, rewriting some-monorepo's origin remote (with the token embedded in the URL) and attempting to hard-reset the entire monorepo onto some-other-repo's content.
In our case this happened three times in one session; it was only prevented from actually destroying data because git checkout refused to proceed due to uncommitted local changes — that's git's own safety net, not gitagent's. A directory with committed state, or a clean tree, would not have been protected.

Suggested fix: Make dir actually required at the type level and at runtime (throw if options.repo.dir is unset — don't fall back to process.cwd()). At minimum, before running any destructive git command, verify the resolved dir either doesn't exist yet (safe to clone into) or its own git rev-parse --show-toplevel equals dir itself (i.e., it's not inside a different repo's working tree).

Bug 2 — finalize() (commit + push) is unconditional, no read-only mode
In sdk.js, localSession.finalize() is called at the end of every successful run and in the top-level .catch() handler on error — there's no option to skip the commit/push step:

// success path
if (localSession) {
try { localSession.finalize(); } catch { /* best-effort / }
}
...
// error path
.catch(async (err) => {
if (localSession) {
try { localSession.finalize(); } catch { /
best-effort */ }
}
...
finalize() → commitChanges() (no-op if nothing staged) then unconditionally push(). When session isn't passed, initLocalSession mints a fresh random branch (gitagent/session-<8-hex>) every call — meaning every single query() invocation with repo: set pushes a new branch to the real remote, even if the model made zero changes (tools disabled, pure Q&A). There's no way to run repo: mode read-only.

Suggested fix: Add an option like repo.readOnly or repo.autoPush: false that skips finalize()/push() entirely, for callers that only want gitagent to read an agent definition from a repo without mutating it.

Why this matters
The combination of these two makes repo: unsafe to wire into any server/request path (as opposed to a one-shot interactive CLI session) — which is a reasonable thing to want, since the SDK is marketed as embeddable ("mirrors the Claude Agent SDK pattern but runs in-process"). Right now the only safe pattern is: never omit dir, always pass a pinned session, and accept that every call still pushes to the remote regardless of intent. PS: Its wiped out my frontend..

Activity

  1. Nivesh353 commented on Jul 26, 2026

    @Nivesh353
    Collaborator

    Thanks for the detailed report @harshit-lyzr and the reproduction steps. I'm looking into this now to verify the behaviour and understand the root cause. I'll update this issue once I've reproduced it or have a fix.

  2. BellmeJoe commented on Jul 27, 2026

    @BellmeJoe

    This is exactly the class of failure getAdvantage's pre-run gate is built to catch: unsafe/dirty working tree state and destructive git operations before an agent's actions land, not after. If it'd help, I'm happy to wire it into your CI as a pre-flight check for this repo, one PR, and show you what it catches in 15 minutes. No contract, pull it out after if it's not useful.

    Repo: https://github.com/BellmeJoe/getadvantage-cli

  3. stealthwhizz commented on Jul 28, 2026

    @stealthwhizz
    Collaborator

    @BellmeJoe It is out of scope..
    Thanks for commenting.. 👍

  4. jhapriyavart10 commented on Sep 20, 2026

    @jhapriyavart10

    Hi @Nivesh353 @shreyas-lyzr,

    I was looking into the repo-mode safety hazards described here and noticed that @Nivesh353 already addressed the core design (directory validation, ancestor escape guards, and readOnly support) in PR #88, which was approved.

    Because current main still exhibits these behaviors and PR #88 currently has merge conflicts following recent main updates, I checked the branch to see where an external contribution might be helpful without duplicating the existing work.

    One area that appears uncovered is automated regression testing for these safety properties:

    1. Rejection when dir is omitted in repo mode.
    2. Protection against escaping into parent/ancestor repositories.
    3. Refusal to reset repositories tracking mismatched remotes.
    4. Skipping commits/pushes under readOnly: true while ensuring auth tokens are scrubbed on exit.

    Would you prefer a focused companion PR adding these hermetic regression tests against the current main, or would you rather leave this entirely with the existing maintainers?

  5. stealthwhizz commented on Sep 22, 2026

    @stealthwhizz
    Collaborator

    Hi @Nivesh353 @shreyas-lyzr,

    I was looking into the repo-mode safety hazards described here and noticed that @Nivesh353 already addressed the core design (directory validation, ancestor escape guards, and readOnly support) in PR #88, which was approved.

    Because current main still exhibits these behaviors and PR #88 currently has merge conflicts following recent main updates, I checked the branch to see where an external contribution might be helpful without duplicating the existing work.

    One area that appears uncovered is automated regression testing for these safety properties:

    1. Rejection when `dir` is omitted in repo mode.
    
    2. Protection against escaping into parent/ancestor repositories.
    
    3. Refusal to reset repositories tracking mismatched remotes.
    
    4. Skipping commits/pushes under `readOnly: true` while ensuring auth tokens are scrubbed on exit.
    

    Would you prefer a focused companion PR adding these hermetic regression tests against the current main, or would you rather leave this entirely with the existing maintainers?

    Hey @jhapriyavart10 , thanks for checking before duplicating effort. Go ahead and open the companion regression-test PR for those four safety properties against current main

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions