Skip to content

Commit 2ac0191

Browse files
committed
Fix upstream sync merge corruption and update mechanism
- Fix garbled Ok(Some(token_set)) match arm in check_auth_health - Add missing detect_broad_cwd and enforce_broad_cwd_policy functions - Add allow_broad_cwd to Repl match arm destructuring - Add missing updated_at_ms field in collect_sessions_from_dir - Fix self-update: use git fetch+reset instead of pull to avoid divergent branch errors
1 parent 5064638 commit 2ac0191

1 file changed

Lines changed: 110 additions & 50 deletions

File tree

  • rust/crates/rusty-claude-cli/src

rust/crates/rusty-claude-cli/src/main.rs

Lines changed: 110 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,7 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
275275
permission_mode,
276276
base_commit,
277277
reasoning_effort,
278+
allow_broad_cwd,
278279
} => {
279280
ensure_hackcode_ready()?;
280281
check_for_updates_async();
@@ -284,6 +285,7 @@ fn run() -> Result<(), Box<dyn std::error::Error>> {
284285
permission_mode,
285286
base_commit,
286287
reasoning_effort,
288+
allow_broad_cwd,
287289
)?;
288290
}
289291
CliAction::HelpTopic(topic) => print_help_topic(topic),
@@ -1237,19 +1239,25 @@ fn run_self_update() -> Result<(), Box<dyn std::error::Error>> {
12371239
// Clone or pull
12381240
if src_dir.join(".git").exists() {
12391241
eprintln!(" {dim}Pulling latest...{nc}");
1240-
let _ = Command::new("git")
1241-
.args(["-C", &src_dir.to_string_lossy(), "checkout", "dev", "--quiet"])
1242+
// Fetch and hard-reset to avoid divergent branch errors
1243+
let fetch = Command::new("git")
1244+
.args(["-C", &src_dir.to_string_lossy(), "fetch", "origin", "dev", "--quiet"])
12421245
.status();
1243-
let pull = Command::new("git")
1244-
.args(["-C", &src_dir.to_string_lossy(), "pull", "--quiet"])
1245-
.status();
1246-
if pull.map_or(true, |s| !s.success()) {
1247-
// If pull fails, re-clone
1246+
if fetch.map_or(false, |s| s.success()) {
1247+
let _ = Command::new("git")
1248+
.args(["-C", &src_dir.to_string_lossy(), "checkout", "dev", "--quiet"])
1249+
.status();
1250+
let _ = Command::new("git")
1251+
.args(["-C", &src_dir.to_string_lossy(), "reset", "--hard", "origin/dev"])
1252+
.status();
1253+
} else {
1254+
// If fetch fails, re-clone
12481255
let _ = std::fs::remove_dir_all(&src_dir);
12491256
let status = Command::new("git")
12501257
.args([
12511258
"clone",
12521259
"--quiet",
1260+
"--branch", "dev",
12531261
"https://github.com/itwizardo/hackcode.git",
12541262
&src_dir.to_string_lossy(),
12551263
])
@@ -1823,49 +1831,28 @@ fn check_auth_health() -> DiagnosticCheck {
18231831
);
18241832

18251833
match load_oauth_credentials() {
1826-
Ok(Some(token_set)) => {
1827-
let expired = oauth_token_is_expired(&api::OAuthTokenSet {
1828-
access_token: token_set.access_token.clone(),
1829-
refresh_token: token_set.refresh_token.clone(),
1830-
expires_at: token_set.expires_at,
1831-
scopes: token_set.scopes.clone(),
1832-
});
1833-
let mut details = vec![
1834-
format!(
1835-
"Environment api_key={} auth_token={}",
1836-
if api_key_present { "present" } else { "absent" },
1837-
if auth_token_present {
1838-
"present"
1839-
} else {
1840-
"absent"
1841-
}
1842-
),
1843-
format!(
1844-
"Saved OAuth expires_at={} refresh_token={} scopes={}",
1845-
token_set
1846-
.expires_at
1847-
.map_or_else(|| "<none>".to_string(), |value| value.to_string()),
1848-
if token_set.refresh_token.is_some() {
1849-
"present"
1850-
} else {
1851-
"absent"
1852-
},
1853-
if token_set.scopes.is_empty() {
1854-
"<none>".to_string()
1855-
} else {
1856-
token_set.scopes.join(",")
1857-
}
1858-
),
1859-
];
1860-
if expired {
1861-
details.push(
1862-
"Suggested action hackcodelogin to refresh local OAuth credentials".to_string(),
1863-
);
1864-
}
1865-
DiagnosticCheck::new(
1866-
"Auth",
1867-
if expired {
1868-
DiagnosticLevel::Warn
1834+
Ok(Some(token_set)) => DiagnosticCheck::new(
1835+
"Auth",
1836+
if api_key_present || auth_token_present {
1837+
DiagnosticLevel::Ok
1838+
} else {
1839+
DiagnosticLevel::Warn
1840+
},
1841+
if api_key_present || auth_token_present {
1842+
"supported auth env vars are configured; legacy saved OAuth is ignored"
1843+
} else {
1844+
"legacy saved OAuth credentials are present but unsupported"
1845+
},
1846+
)
1847+
.with_details(vec![
1848+
env_details,
1849+
format!(
1850+
"Legacy OAuth expires_at={} refresh_token={} scopes={}",
1851+
token_set
1852+
.expires_at
1853+
.map_or_else(|| "<none>".to_string(), |value| value.to_string()),
1854+
if token_set.refresh_token.is_some() {
1855+
"present"
18691856
} else {
18701857
"absent"
18711858
},
@@ -3273,6 +3260,78 @@ fn run_stale_base_preflight(flag_value: Option<&str>) {
32733260
}
32743261
}
32753262

3263+
fn detect_broad_cwd() -> Option<PathBuf> {
3264+
let Ok(cwd) = env::current_dir() else {
3265+
return None;
3266+
};
3267+
let is_home = env::var_os("HOME")
3268+
.or_else(|| env::var_os("USERPROFILE"))
3269+
.is_some_and(|h| Path::new(&h) == cwd);
3270+
let is_root = cwd.parent().is_none();
3271+
if is_home || is_root {
3272+
Some(cwd)
3273+
} else {
3274+
None
3275+
}
3276+
}
3277+
3278+
fn enforce_broad_cwd_policy(
3279+
allow_broad_cwd: bool,
3280+
output_format: CliOutputFormat,
3281+
) -> Result<(), Box<dyn std::error::Error>> {
3282+
if allow_broad_cwd {
3283+
return Ok(());
3284+
}
3285+
let Some(cwd) = detect_broad_cwd() else {
3286+
return Ok(());
3287+
};
3288+
3289+
let is_interactive = io::stdin().is_terminal();
3290+
3291+
if is_interactive {
3292+
eprintln!(
3293+
"Warning: hackcode is running from a very broad directory ({}).\n\
3294+
The agent can read and search everything under this path.\n\
3295+
Consider running from inside your project: cd /path/to/project && hackcode",
3296+
cwd.display()
3297+
);
3298+
eprint!("Continue anyway? [y/N]: ");
3299+
io::stderr().flush()?;
3300+
3301+
let mut input = String::new();
3302+
io::stdin().read_line(&mut input)?;
3303+
let trimmed = input.trim().to_lowercase();
3304+
if trimmed != "y" && trimmed != "yes" {
3305+
eprintln!("Aborted.");
3306+
std::process::exit(0);
3307+
}
3308+
Ok(())
3309+
} else {
3310+
let message = format!(
3311+
"hackcode is running from a very broad directory ({}). \
3312+
The agent can read and search everything under this path. \
3313+
Use --allow-broad-cwd to proceed anyway, \
3314+
or run from inside your project: cd /path/to/project && hackcode",
3315+
cwd.display()
3316+
);
3317+
match output_format {
3318+
CliOutputFormat::Json => {
3319+
eprintln!(
3320+
"{}",
3321+
serde_json::json!({
3322+
"type": "error",
3323+
"error": message,
3324+
})
3325+
);
3326+
}
3327+
CliOutputFormat::Text => {
3328+
eprintln!("error: {message}");
3329+
}
3330+
}
3331+
std::process::exit(1);
3332+
}
3333+
}
3334+
32763335
#[allow(clippy::needless_pass_by_value)]
32773336
fn run_repl(
32783337
model: String,
@@ -5025,6 +5084,7 @@ fn collect_sessions_from_dir(
50255084
sessions.push(ManagedSessionSummary {
50265085
id,
50275086
path,
5087+
updated_at_ms: modified_epoch_millis as u64,
50285088
modified_epoch_millis,
50295089
message_count,
50305090
parent_session_id,

0 commit comments

Comments
 (0)