Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t.sh

Commit

Sessions g1t starts on Claude Managed Agents can push a new repository's first version: on flagon-io/demo #1 the session built the page, then its `git commit && git push origin main` was blocked by the sandbox's permission check (permission_policy auto), so nothing went live. Two things made it look unsafe. The kickoff, the only user turn, said "make the change on a branch, open a draft pull request" and called the issue reference material, so nothing the person wrote asked for a push to the default branch (Ask's new-repository card says so in its instruction; a session started by @g1t in a comment didn't). And the agent set its own identity, `git -c user.name=syntaqx -c user.email=…`, to commit as the person. The kickoff's implement step now says that when the repository has no commits yet there is nothing to open a pull request against, so the first version goes straight to the default branch, and that this push is asked of it. The setup command sets git's user.name and user.email to the person who started the session (their username and noreply address), and the system prompt says never to set another author. The sessions guide says both. Checked: cargo test -p g1t-integrations (76), wasm32 check of g1t-integrations.

G1t-Session:
ses_01m4z09g22e7ysqpg2ha423cws
syntaqxcommitted Parent25309b9Browse files
2 files+31−70/2 viewed
+5−1
282282 session on that account, with its key, on the model Ask is set to use
283283 (or `claude-opus-5-5`). It runs in a sandbox on Anthropic's side. Its
284284 first step installs the [g1t CLI](/guides/cli/) from g1t, sets git up to
285− send the token, clones the repository and ties the clone to the session,
285+ send the token and to commit as you (your username and noreply address),
286+ clones the repository and ties the clone to the session,
286287 so it works the way the work-on-issue skill describes: `g1t work
287288 context`, `g1t work claim`, `g1t pr create`, `g1t pr checks --watch`.
289+ It works on a branch and a pull request, except in a repository with no
290+ commits yet, where there is nothing to open one against: it pushes the
291+ first version straight to the default branch.
288292 g1t's MCP server is attached too, for anything the CLI has no command
289293 for. Both reach g1t with a token made for you
290294 and that repository only, which lasts the session and is revoked when it
+26−6
114114 never in a URL. Never print it or send it anywhere else.\n\
115115 - Follow the work-on-issue skill. Check the work context and the sessions on the issue before you start, and claim only what you take. \
116116 When work splits into independent parts, you may give a part to a copy of yourself; each is recorded on g1t as a subagent and sees the others.\n\
117− - Put the trailer `G1t-Session: <the session id in your first message>` on every commit, so g1t ties the commits to this session.\n\
117+ - Put the trailer `G1t-Session: <the session id in your first message>` on every commit, so g1t ties the commits to this session. \
118+ The setup makes your commits the person's who started you: never set another author or committer.\n\
118119 - Work on a branch whose name starts with the issue number, and open a draft pull request early. Never push to the default branch, \
119120 except a repository's first commit when it has none yet: there is nothing to open a pull request against, so the first version goes \
120121 straight to it.\n\
309310 pub session_id: Option<&'a str>,
310311 /// For a session g1t starts: how to reach the repository.
311312 pub managed: bool,
313+ /// For a session g1t starts: who its commits are by, the person who
314+ /// started it (name, address), so it never sets an identity itself.
315+ pub author: Option<(&'a str, &'a str)>,
312316 }
313317
314318 fn cut(text: &str, max: usize) -> String {
362366 text.push_str(match (k.mode, &k.pull) {
363367 (AgentMode::Plan, _) => "Plan it: work out how it should be done and post the plan as a comment on the issue. Change no code and open no pull request.",
364368 (AgentMode::Implement, Some(_)) => "Push the change to the pull request's branch, get its checks passing, and keep it a draft until it is ready.",
365− (AgentMode::Implement, None) => "Implement it: make the change on a branch, open a draft pull request for the issue early, get its checks passing, and mark it ready.",
369+ (AgentMode::Implement, None) => {
370+ "Implement it: make the change on a branch, open a draft pull request for the issue early, get its checks passing, and mark it ready. \
371+ If the repository has no commits yet, there is nothing to open a pull request against: \
372+ commit the first version straight to the default branch and push it there. That push is asked of you."
373+ }
366374 (AgentMode::Review, _) => "Review it: read the issue's pull request and its checks, and review it on g1t with what should change, if anything.",
367375 });
368376 text.push('\n');
370378 text.push_str(&format!("\nAlso: {}\n", cut(instruction, MAX_INSTRUCTION_CHARS)));
371379 }
372380 if k.managed {
373− text.push_str(&format!("\nSet up first, as one command:\n\n```sh\n{}\n```\n", setup_command(site, &repo, &k.repo.name, k.session_id)));
381+ text.push_str(&format!("\nSet up first, as one command:\n\n```sh\n{}\n```\n", setup_command(site, &repo, &k.repo.name, k.session_id, k.author)));
374382 text.push_str(&format!(
375383 "\nIf /usr/local/bin cannot be written, install without G1T_INSTALL_DIR and run ~/.local/bin/g1t. Then work in /workspace/{}.\n",
376384 k.repo.name
386394 /// What a sandbox runs before it works: the g1t CLI from the site, git
387395 /// sending `G1T_TOKEN` to the site in a header (the vault swaps the
388396 /// placeholder there), the clone, and the clone's g1t session where the
389−/// CLI keeps it, so claims and notes are this session's.
390−pub fn setup_command(site: &str, repo: &str, name: &str, session_id: Option<&str>) -> String {
397+/// CLI keeps it, so claims and notes are this session's. Commits are by
398+/// the person who started it.
399+pub fn setup_command(site: &str, repo: &str, name: &str, session_id: Option<&str>, author: Option<(&str, &str)>) -> String {
391400 let site = site.trim_end_matches('/');
392401 let mut steps = vec![
393402 format!("curl -fsSL {site}/install | G1T_INSTALL_DIR=/usr/local/bin sh"),
394403 format!("git config --global http.{site}/.extraHeader \"Authorization: Bearer $G1T_TOKEN\""),
395− format!("git clone {site}/{repo}.git /workspace/{name}"),
396404 ];
405+ if let Some((author, email)) = author {
406+ steps.push(format!("git config --global user.name {}", shell_quote(author)));
407+ steps.push(format!("git config --global user.email {}", shell_quote(email)));
408+ }
409+ steps.push(format!("git clone {site}/{repo}.git /workspace/{name}"));
397410 if let Some(id) = session_id {
398411 steps.push(format!("mkdir -p /workspace/{name}/.git/g1t && echo {id} > /workspace/{name}/.git/g1t/session"));
399412 }
980993 Outcome::Fail(failure) => return Ok(Outcome::Fail(failure)),
981994 };
982995 let pull_number = issue.pull.as_ref().map(|(number, _, _)| *number);
996+ let author_email = g1t_contracts::accounts::noreply_address(&actor.id, &actor.username);
983997 let kickoff_of = |session_id: Option<&str>, managed: bool| {
984998 kickoff(&Kickoff {
985999 site: &self.site_url,
9931007 instruction: a.instruction.as_deref(),
9941008 session_id,
9951009 managed,
1010+ author: managed.then_some((actor.username.as_str(), author_email.as_str())),
9961011 })
9971012 };
9981013 // The hand-off: nothing starts here.
15801595 instruction: Some("Keep it short."),
15811596 session_id: Some("ses_1"),
15821597 managed: true,
1598+ author: Some(("ada", "6c1d0efg+ada@users.noreply.g1t.sh")),
15831599 });
15841600 for text in [session.to_string(), prompt.clone()] {
15851601 assert!(!text.contains("g1t_secret"));
15901606 assert!(prompt.contains("curl -fsSL https://g1t.sh/install | G1T_INSTALL_DIR=/usr/local/bin sh"));
15911607 assert!(prompt.contains("git config --global http.https://g1t.sh/.extraHeader \"Authorization: Bearer $G1T_TOKEN\""));
15921608 assert!(prompt.contains("git clone https://g1t.sh/acme/web.git /workspace/web"));
1609+ assert!(prompt.contains("git config --global user.name 'ada'"));
1610+ assert!(prompt.contains("git config --global user.email '6c1d0efg+ada@users.noreply.g1t.sh'"));
1611+ assert!(prompt.contains("commit the first version straight to the default branch and push it there"), "an empty repository's first push is asked for");
15931612 assert!(prompt.contains("echo ses_1 > /workspace/web/.git/g1t/session"));
15941613 assert!(prompt.contains("G1t-Session: ses_1"));
15951614 let environment = environment_body("g1t-acme-con_1", &hosts);
16121631 instruction: None,
16131632 session_id: None,
16141633 managed: false,
1634+ author: None,
16151635 })
16161636 };
16171637 assert!(prompt(AgentMode::Plan).contains("Change no code"));

Comments

Sign in to comment.