Say the resolver refusal once, and hand over the fix
Running it for real turned up the predicted polkit refusal — InteractiveAuthorizationRequired — and two things wrong with how the agent handled it. It logged the same line every two seconds. A condition that persists is worth saying once, so it is now repeated only when the message changes. It also retried at that pace. A refusal will not lift until somebody grants permission, so retrying it as often as everything else is noise: refusals now back off to five minutes, other failures to fifteen seconds, and either resets the moment it succeeds or the desired setting changes. The more useful part: the agent prints the polkit rule that grants it, ready to paste, naming the user it is running as. polkit decides by user and not by capability, so this genuinely cannot be arranged from inside the process — which makes "write a polkit rule" the user's work, and handing them the rule rather than describing it is the difference between a minute and an afternoon. It grants the four actions the agent calls and nothing else; a test pins both halves of that, and that the JavaScript stays within what duktape implements. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
+38
-3
@@ -568,6 +568,12 @@ fn spawn_dns(
|
||||
// time for no reason.
|
||||
let mut attempted: Vec<SocketAddr> = Vec::new();
|
||||
let mut published: Option<tsunagi::dns::Published> = None;
|
||||
// A condition that persists is worth saying once, not every
|
||||
// pass; and a refusal will not lift without somebody acting, so
|
||||
// hammering at it two seconds apart is pure noise.
|
||||
let mut reported: Option<String> = None;
|
||||
let mut retry_after: Option<tokio::time::Instant> = None;
|
||||
let mut recipe_shown = false;
|
||||
let mut ticker = tokio::time::interval(std::time::Duration::from_secs(2));
|
||||
|
||||
loop {
|
||||
@@ -654,7 +660,8 @@ fn spawn_dns(
|
||||
server: address,
|
||||
domains: vec![zone.as_str().to_string()],
|
||||
};
|
||||
if published.as_ref() != Some(&want_published) {
|
||||
let due = retry_after.is_none_or(|at| tokio::time::Instant::now() >= at);
|
||||
if published.as_ref() != Some(&want_published) && due {
|
||||
match publisher.apply(&want_published).await {
|
||||
Ok(()) => {
|
||||
tracing::info!(
|
||||
@@ -663,6 +670,8 @@ fn spawn_dns(
|
||||
"the system resolver was told where to ask"
|
||||
);
|
||||
published = Some(want_published);
|
||||
reported = None;
|
||||
retry_after = None;
|
||||
update(&state, |state| {
|
||||
state.publish_error = None;
|
||||
state.publish_remedy = None;
|
||||
@@ -671,10 +680,29 @@ fn spawn_dns(
|
||||
Err(err) => {
|
||||
// Not fatal, by design: the server keeps
|
||||
// answering and the user is told what is missing.
|
||||
tracing::warn!(%err, "cannot configure the system resolver");
|
||||
let text = err.to_string();
|
||||
if reported.as_deref() != Some(text.as_str()) {
|
||||
tracing::warn!("cannot configure the system resolver: {text}");
|
||||
if err.needs_a_human() && !recipe_shown {
|
||||
recipe_shown = true;
|
||||
tracing::warn!(
|
||||
"systemd-resolved asks polkit, and polkit decides by \
|
||||
user rather than by capability, so this cannot be done \
|
||||
from inside the agent. To grant it once:\n\n{}\n",
|
||||
tsunagi::dns::publish::polkit_recipe(¤t_user())
|
||||
);
|
||||
}
|
||||
reported = Some(text.clone());
|
||||
}
|
||||
// Backed off, and further for something only a
|
||||
// person can change.
|
||||
let wait = if err.needs_a_human() { 300 } else { 15 };
|
||||
retry_after = Some(
|
||||
tokio::time::Instant::now() + std::time::Duration::from_secs(wait),
|
||||
);
|
||||
let remedy = err.remedy().map(str::to_string);
|
||||
update(&state, |state| {
|
||||
state.publish_error = Some(err.to_string());
|
||||
state.publish_error = Some(text);
|
||||
state.publish_remedy = remedy;
|
||||
});
|
||||
}
|
||||
@@ -1812,6 +1840,13 @@ mod report {
|
||||
}
|
||||
}
|
||||
|
||||
/// The user this process is running as, for an instruction it can paste.
|
||||
fn current_user() -> String {
|
||||
std::env::var("USER")
|
||||
.or_else(|_| std::env::var("LOGNAME"))
|
||||
.unwrap_or_else(|_| "<your-user>".to_string())
|
||||
}
|
||||
|
||||
/// This program's path, for an instruction the user can paste.
|
||||
fn program_path() -> String {
|
||||
std::env::current_exe()
|
||||
|
||||
Reference in New Issue
Block a user