Repository navigation
Headless agy: a soft-denied read is a denied round, and the ADC token is probed before a gcp spawn (2.8.0) - #34
Merged
Merged
Conversation
…y answer is a denied round agy soft-denies a read_file outside the workspace in plan mode and ends the turn with no answer, but its stream shows no tool error: only the result's denied_actions names it. denied_outcome() read the stream's denied events alone, and the denied_actions fallback ran after the outcome was fixed, so the round reported status succeeded, exit 0, last_message null. Replay.refusals() now yields the stream's refusals or, failing those, one entry per counted action, and both _finalise() and adopt() decide from it. The error for a refused read_file names the read_file(<absolute dir>) rule and --extra --add-dir. The agy stub gains STUB_SOFT_DENY with the live shape (step DONE, log line, empty response); the module docstrings say a soft deny can end the turn.
…e stream, the log and the count The Codex plan review found three readers that still said succeeded on a soft-denied round: cairn invoke tail printed the stream's own status when the final event arrived before the runner finalised, status classified from that status and showed an earlier progress message as the answer, and an adopted denied round exited 1 with a null exit code. stream_outcome() applies the runner's classification to a stream any reader sees, EXIT_BY_OUTCOME gives adopt and tail the same exit codes as the runner, tail waits a moment for the runner's record before falling back, and a finished round's record is authoritative for status (last_message None included). Replay.refusals() merges the stream's refusals, the soft denials observed in agy's log and one entry per counted action type the others did not name, with agy's 'command' action mapped to run_command. The adopt test is its own method: the earlier one picked the finished thread.
…spawn
A soft-denied read_file had no path: agy's log names it by step ('soft-denying tool
confirmation "ReadFile" at step 2'), so the agy harness keeps a step-to-target map from
the stream and joins the two (Antigravity.soft_denials); the live runner reads the log
before the outcome is decided and adopt reads it from either location. read_verdict()
models agy's documented read rules (deny beats all, read_file(<dir>) covers its subtree,
write_file grants read, a bare target matches the name, the workspace and every --add-dir
need no rule) so the error and the resume note say whether a refused path is now readable,
still denied, or still outside the workspace; the round records its roots.
The ADC file can exist with a refresh token Google no longer honours (invalid_rapt, the
Workspace session-length policy). adc_token_check() runs gcloud's print-access-token with
the run's environment: a rejected credential refuses the spawn (exit 2) and is a warn line
in cairn doctor with the login command; gcloud missing, a timeout or a transport failure
is a warning, never a refusal, and doctor says the token was not verified. observe_billing
reads 'adcAuth: failed to get token from token source' when no success follows, so a
failed round names the cause (not retryable for a rejected credential, retryable for a
transport failure). A gcloud stub joins the test fixture.
The invoke-antigravity skill says which credential each billing route needs, that agy models can fail while a gcp run works, that plan mode reads the --cd tree only (files outside it go inside, through --extra --add-dir, or into the prompt), and that a soft deny can end the turn. The anonymised post-mortem of the two dead rounds lands in docs with the resolution, and the changelog carries 2.8.0.
…n every reader, narrower ADC classification From the Codex review of the diff (gpt-6-astra, high). read_verdict() now takes agy's ask rules (headless agy cannot ask, so they deny), resolves a relative rule target and a relative --add-dir against the workspace, normalises dot segments, matches a slash glob against the whole path and treats a trailing slash as the same rule; the round records permissions.ask. status and tail load the log's soft denials like the runner and adopt do, and an observation classified denied shows no progress message as the answer. A rejected credential is recognised by 'invalid_grant', 'invalid_rapt', 'reauth related error' or 'does not have any valid credentials', no longer by the bare word 'reauth'; the ADC error from agy's log replaces a round's error only when that error is about authentication, and any later applyAuthResult counts as recovery. The probe's stdin is /dev/null. The skill and changelog describe both denial shapes seen live, say the --add-dir flag is recorded but not reapplied, name thread.json as where permissions live, and give the probe's 20 s bound. The foundations test module's main guard sits last.
…the log after its grace, slashless read rules, a narrower auth filter cairn invoke wait could build a result from an unfinished round while another reader held the thread lock and was adopting it (status and exit code null); it now holds up to 30 s for that reader's record. tail read the peer's log before its grace wait, so a denial the log showed during the wait was missed; it reads the log after the wait. A slashless read_file target now also reads as a workspace-relative path or directory (the union with the name match, since the two readings have not been told apart live), and a root of '/' covers everything. The ADC log error replaces a failed round's error only for text about authentication (not 'input token count'). The changelog and post-mortem say plan mode reads the --cd tree with no rule, not only; the skill says wait holds for an adopting reader. Tests: the tail fallback after its grace, wait under a held lock, the new rules.
…round; whole-word auth filter Past its hold (the peer's quiet_after_s), cairn invoke wait reports status busy (exit 75) with the reason instead of a result with null status, outcome and exit code. The authentication filter matches whole words, so 'readconfig' no longer counts as ADC. The two race tests now exercise the race: the record is stripped again after status adopted it, the log's denial appears during tail's grace wait, and the waiter is checked to be still holding before the lock is released.
…tion; tail follows to the record; ask rules in command verdicts gcloud with no terminal (the probe's case) reports a rejected refresh token as 'Reauthentication failed. cannot prompt during non-interactive execution', with no OAuth code, so the probe warned and spawned on the very credential it exists to catch; that text now refuses, and the gcloud stub emits it (the with-terminal shape is a second mode). tail no longer trusts the stream after a fixed grace: it follows a live runner to its record (the watchdog may still make the round a timeout), adopts the round when both processes are gone, and holds for another reader's adoption up to the peer's quiet threshold before classifying the stream plus the log itself. Command verdicts and the resume-note diff take agy's ask rules into account (a refusal when headless; ask beats allow). The error counts tool calls only when every entry has a target; a targetless entry is an action type agy counted. Tests cover each; the tail test under a held lock exercises the hold.
…er; a snapshot of a live round is not a verdict; the remedy follows the verdict In tail's adoption hold, observe() could land another reader's record (a cancellation, say) in the last poll and the loop still fell back to the stream: the round is reloaded right after each observation and a finished record always wins. With --no-follow, a round whose runner or peer is still alive is reported running, not with the stream's own status. The denied error says 'Review the deny and ask rules' when every refusal is covered by such a rule (adding an allow rule would not help) and 'Add the rule' only for an unmatched call. Refusals are counted as distinct refusal(s) everywhere: entries are (tool, target) pairs or counted action types, never exact calls. The tail hold test asserts the hold took its time.
…oth processes are gone; the remedy covers a mixed set With --no-follow, tail read the record, saw the stream's SUCCESS, found both processes gone and used the stale record while the runner had just saved a timeout; it now reloads the round at that point and a finished record wins. The denied error derives its remedy from the verdicts (command_verdict, read_verdict) rather than from rendered text: review the deny and ask rules for a covered refusal, add the missing allow rule for an unmatched one, both for a mixed set.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The fix for the two dead rounds in
docs/antigravity-soft-deny-postmortem.md, 27 October: a headlessagyrun on billing routegcpwhose ADC refresh token Google had expired (invalid_rapt) failed in 2.4 s whilecairn doctorsaid ok, and a plan-mode run that was soft-denied oneread_fileoutside the workspace ended with no answer and reportedstatus succeeded, exit 0.Engine
denied_actionsnames it, and cairn read that after the outcome was fixed.Replay.refusals()merges the stream's tool errors, the soft denials agy's log names and one entry per counted action type the others did not name. The live runner,adopt,cairn invoke tail(which now waits briefly for the runner's record) andstatus(a finished round's record is authoritative,last_message: nullincluded) all reachdenied; an adopted denied round exits 2 like a live one.soft-denying tool confirmation "ReadFile" at step 2); the harness keeps a step-to-target map from the stream and joins the two.read_verdict()models agy's documented read rules (deny beats all,read_file(<dir>)covers its subtree,write_filegrants read, a bare target matches the name, the workspace and every--extra --add-dirneed no rule; the round records thoseroots), so the error and the resume note saynow readable (inside <dir>),still denied by read_file(.env*)orstill outside the workspace with no read_file rule.gcpspawn.adc_token_check()runsgcloud auth application-default print-access-tokenwith the run's environment and discards the token. A rejected credential refuses the spawn (exit 2) with the login command and is a warn line incairn doctor; gcloud missing, a timeout or a transport failure is abilling:warning and doctor saystoken not verified.adcAuth: failed to get token from token sourcein agy's log (with no success after it) becomes the round's error, not retryable for a rejected credential.Skill and docs
skills/invoke-antigravity/SKILL.md: which credential each billing route needs,agy modelscan fail while agcprun works, plan mode reads the--cdtree only (files outside go inside, through--extra --add-dir <dir>repeated on every resume, or into the prompt), and thedeniedandbilling:cases.docs/antigravity-soft-deny-postmortem.mdis the anonymised post-mortem with the resolution. Version 2.8.0.Review
The plan was reviewed adversarially by Codex (gpt-6-astra, high) before the build. It found three readers that still said
succeededon a soft-denied round (tail,status, an adopted round's exit code), confirmed by a probe; mixed refusal sources dropped; a read verdict that could not see--add-dirand ignored deny-over-workspace; every ADC failure made non-retryable; doctor green on an unverified probe; the skill still demandingagy models. All fixed here. It cut a prompt scan for paths outside the workspace (heuristic) and rejected an automatic--add-dir <prompt-file dir>. Its suggestion to split denial handling and ADC readiness into two PRs was not taken: both faults come from one incident and one document, as 2.7.0 did.Codex then reviewed the diff (thread
01a17d84-933c-70d3-9108-474b495d0e07, reject, eight findings), all verified and fixed in the last commit:askrules were ignored (they deny headlessly); a relative rule target was compared against an absolute path, soread_file(secret/)in deny let the workspace default win;tailandstatusdid not load the log's soft denials; the ADC error from agy's log replaced any failed round's error and flippedretryable; the bare wordreauthin gcloud's stderr counted as a rejected credential; an observation classified denied before finalisation still showed a progress message as the answer; the skill and changelog claimed one denial shape when live runs show two, and said--extrais not recorded when it is recorded but not reapplied; the foundations test module's main guard sat before two new classes. A third round on the fix commit found four of the eight only partly fixed and showed that the two untaken tests hid real defects:waitbuilt a result from an unfinished round while another reader was adopting it, andtailread the log before its grace wait. The last commit fixes both with tests, reads a slashless rule target also as a workspace-relative path, lets a root of/cover everything, and narrows the authentication-text filter so a token-limit error keeps its own text.A fourth round confirmed the path and tail fixes and left two defects, fixed in the last commit: the authentication filter matched
adcinsidereadconfig(now whole words), andwaitstill built a null result from an unfinished round once its hold expired (nowstatus: busy, exit 75, bounded by the peer'squiet_after_s). Two race tests thatstatushad short-circuited now exercise the races.A fresh Codex thread then reviewed the whole branch and found one high defect: gcloud run without a terminal reports a rejected token as "Reauthentication failed. cannot prompt during non-interactive execution", with no OAuth code, so the probe would have warned and spawned on the very credential it exists to catch. Fixed with that text as a rejection marker and the stub emitting it. Also fixed from that round:
tailnow follows a live runner to its record instead of trusting the stream after a fixed grace,askrules count in command verdicts and in the resume-note diff, and the error counts tool calls only when every entry has a target.Its follow-up confirmed the probe fix and found the tail hold could still discard a record another reader finished in the last poll, the
--no-followsnapshot reported the stream's status while the peer was alive, the remedy said "add an allow rule" for a refusal a deny or ask rule caused, and the counts still read as exact calls. All four fixed. Its last round (the thread's third) confirmed those and left two small items, fixed in the final commit: the--no-followsnapshot now reloads the record once it finds both processes gone, and a mixed set of refusals gets both remedies, derived from the verdicts. Codex also notes that the two race tests rely on timing (a held lock plus elapsed time, a poll before release) rather than a handshake; that limitation stands.Verification
make checkgreen (475 tests) on every commit. Live against agy 1.4.8 on routegcp:cairn doctorprintstoken ok; a plan-mode run told toread_filean invented file outside--cdendedstatus denied, exit 2,denied: [{tool: read_file, target: <that path>}], the error namingread_file(<its dir>)and--extra --add-dir, andcairn invoke tailprintedTOOL read_file,DENIED read_file <path>,RESULT denied; the resume with--extra --add-dir <dir>got the notepreviously refused read_file <path>: now readable (inside <dir>), recorded the directory inpermissions.roots, and answered with the file's first line (status succeeded, exit 0).