diff --git a/memories/gha-reusable-workflow-permissions.md b/memories/gha-reusable-workflow-permissions.md index e4d4c0ed..68cbce2b 100644 --- a/memories/gha-reusable-workflow-permissions.md +++ b/memories/gha-reusable-workflow-permissions.md @@ -43,6 +43,26 @@ completes with `startup_failure` and an empty `jobs` array. - **Do:** see `github-actions.md`'s "GitHub Actions workflow authoring gotchas" for the permission-grant mechanism and remedy; this only adds where to find the error text once you've hit it. +- **Confirmed again, different repo, different permission**: + `Morrison-Lab/psw`'s `claude-code-review.yml` caller granted `contents`, + `pull-requests`, `issues`, `id-token` but omitted `actions`, while the + callee's `claude-review` job requests `actions: read` (to let + `claude-code-action` install its `github_ci` MCP server). Same + `startup_failure`, zero jobs, nothing in `pull_request_read` + `get_check_runs` or `get_job_logs`. `WebFetch` on the run's `html_url` + reliably surfaced the Annotations text verbatim -- "The nested job + 'claude-review' is requesting 'actions: read', but is only allowed + 'actions: none'." -- confirming this isn't `d-morrison/qwt`-specific and + that a plain `WebFetch` (not just a dedicated `get_page_text` browser + tool) is sufficient to read it. (Morrison-Lab/psw#43/#44, 2026-08-10.) + This exact `actions: read` gap -- same four granted permissions, same + `startup_failure`/zero-jobs shape -- had already happened once before, + in `ai-config` itself rather than a downstream consumer + ([`github-actions.md`](github-actions.md), ai-config#224). + So this is the third occurrence, not the second, and "not + `d-morrison/qwt`-specific" above undersells it -- the gap recurs + independently of which repo is calling `Morrison-Lab/gha`, ai-config's + own repo included. ## A caller-level `concurrency:` group with the same name as a nested job's own group deadlocks the run diff --git a/memories/github-mcp-tools.md b/memories/github-mcp-tools.md index ad4f6d82..0aa8e63a 100644 --- a/memories/github-mcp-tools.md +++ b/memories/github-mcp-tools.md @@ -381,6 +381,53 @@ See ai-config#694 for the precedent. leave a broken file on the branch waiting for the next review round to catch it. (Hit on lab-manual#376: an editing slip sent a truncated placeholder instead of the real fragment text; caught by checking the returned `size`.) + **Confirmed again with a different downstream symptom, and it defeats a + naive round-trip check.** Passing an already-base64-encoded string produced + a `size` of 2310 bytes for content that should have been 1710 -- a ~4/3 + inflation, the base64 expansion ratio, rather than a suspiciously *small* + number this time. The GitHub Actions symptom was different too: since the + stored blob was a bare base64 scalar rather than a YAML mapping, the + workflow read as having no triggers at all -- a dispatch-time `422 + Workflow does not have 'workflow_dispatch' trigger` on that ref (dispatch + to the unmodified default branch worked fine), and the push itself + produced a generic `failure` conclusion with zero jobs (not + `startup_failure`, which is the permissions-cascade shape covered in + [`gha-reusable-workflow-permissions.md`](gha-reusable-workflow-permissions.md)). + A naive "does it decode without erroring" + round-trip check does not catch this: base64-decoding what + `get_file_contents` reads back just undoes your own accidental encoding + and returns the intended text, which looks like confirmation. The `size` + comparison against the source's real byte length is the check that + actually discriminates. (Morrison-Lab/psw#44, 2026-08-10.) + **A third instance is not an encoding mistake at all --- the `content` + parameter can simply be constructed wrong.** + A follow-up call meant to correct the two case records above instead sent + a literal placeholder string as the whole file body, caught immediately + by `content.size` reading 21 bytes for a ~50KB file. + **A local clone plus a real `git push` avoids this class of mistake + entirely, when push is available.** + `git clone --depth 1 --filter=blob:none --sparse` plus `git push` from + that clone worked in this same session, for a branch that was neither + harness-assigned nor the working directory's own repo --- consistent + with [`github.md`](github.md)'s "the proxy allows branch creation/push + but BLOCKS branch deletion." + `git config -l` showed no local credential (only + `http.proxyauthmethod=basic` and `credential.interactive=false`, no + `~/.git-credentials` or `~/.netrc`), so authentication happens somewhere + in the outbound proxy layer rather than the checkout --- consistent with + this environment's outbound HTTPS being proxied, though the exact + mechanism wasn't traced further. + Once a branch exists to push to, prefer editing the file locally and + pushing over `create_or_update_file`/`push_files` for anything beyond a + trivial edit: the committed content is exactly what `git diff` shows, and + `git hash-object` verifies it byte-for-byte before AND after the push, + with no encoding step or parameter-construction step for a mistake to + hide in. + Not every session gets this --- some are restricted to the + harness-assigned branch only, or fully read-only, per + [`github-actions.md`](github-actions.md)'s "403 caveat" and "fully + READ-ONLY" entries --- so test with a throwaway push before relying on + it. - **Issue *writes* 404 while *reads* succeed → the issue was transferred to another repo, not a permissions gap.** If `mcp__github__add_issue_comment` / `issue_write` to `owner/repo#` fail (`404 Not Found`, or `Could not resolve @@ -708,4 +755,3 @@ See ai-config#694 for the precedent. where the table actually lives. (Caught in ai-config#137 review: the gip skill referenced a table ai-config didn't have at the time; ai-config#327 later added `tool-mappings.md` to close that gap.) -