Repository navigation
feat: rosebud pull, file-by-file uploads, and scoped login errors (0.2.0) - #5
Conversation
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…uploading Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ed its upload The handle's files and media now have to be plain data, or the CLI answers state_format. The lost-response test also asserts the interrupted update staged exactly one PUT, so it proves the rerun reused it. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
pull reads the game's current files, keeps local edits, takes Rosebud's changes and lists files changed on both sides as conflicts, with Rosebud's copy saved beside the state file. A conflicted pull leaves content_version alone, so update still needs --expected-version. Update conflicts now pass changed_paths through. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
--force now settles only what Rosebud changed; files changed or deleted only here stay as they are. next_action commands carry --state and quote paths with spaces, name copies only when there are some, and mention skipped files in both outcomes. Pull stages every write outside the folder, removes before it writes, reports a file/folder clash as path_conflict, reports a repeated remote path as duplicate_path, and ignores hidden entries and leftover .part files in the folder. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…e uploads Update conflicts carry changed_paths, limits always show as numbers (also from servers that send a digit string), and errors add a next_action with its commands in backticks. A 503 busy answer is a rejection that changed nothing: it exits 3 with retry_after_seconds, keeps an update's saved operation for an identical rerun, and never drops a resumed asset create's saved request. An upload session that answers 409 upload_retry is asked for again after Retry-After, up to 20 times. The README and help describe rosebud pull, the media limits and the per-upload timeout allowance. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A busy answer while assets create polls its job is waited out like a throttle. A busy refusal of an update is an ordinary known rejection, so its saved operation is cleared (a retry of an earlier uncertain attempt still keeps it) and a changed rerun, or pull then update, works. The pending_operation error now names --expected-version as the way out. Busy text says "shortly" without a usable Retry-After, upload_retry is only repeated for HTTP 409, and the README states the per-update media limit, the full name rules and the automatic retries that exist. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- Skip OS junk (.DS_Store, Thumbs.db, desktop.ini, any case) when packing and pulling instead of refusing it or shipping it; other hidden names still refuse with unsafe_path. - Refuse whitespace-only names, exclude names ending in .env, and compare excluded names trimmed. - Treat .svg/.gltf outside assets/ as text, and send paths Rosebud holds as text (present in the saved manifest but not in its media list) as text even with a media extension, through update and the one-ZIP fallback. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- On a case-insensitive disk, a path pull would write that differs only by letter case from a local file that stays is a case_conflict: the local file is never overwritten, Rosebud's copy goes to the .remote folder, and next_action names both files. A file the same pull removes is no rival, so a plain Rosebud-side rename by case still goes through. Case-sensitive disks are untouched. - Correct the localFiles comment about hidden entries and OS junk, tighten the vacuous "a is a folder" assertion, give the ten-second upload_retry cap test an explicit timeout, rename the misleading busy429 helper, and say in the README and CHANGELOG that a busy refusal allows changing files unless an earlier update is still uncertain. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… junk rules - Keep repeating an identical upload-session request while Rosebud's `remaining` count falls; stop and surface the error when it fails to fall twice in a row, after 500 attempts, or (when `remaining` is missing or not a number) after the old 20 attempts. - Hidden means a raw leading dot, as on the server, so " .env" is an excluded credential file rather than a hidden one. - Only a regular file named like OS junk is skipped; a folder or link with such a name is walked or refused as usual, in the packer and in pull. - Name the local file in pull's JSON as `clashes_with` on a case_conflict, say in the README that --force does not overwrite a case rival that stays, and state what pull does with a junk-named file Rosebud holds. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ami (ENG-4834) Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ollide there Pull compared paths byte for byte, but macOS and Windows disks fold letter case and macOS folds Unicode form, so files were silently overwritten. - Probe the disk once per pull (a scratch file in the .remote staging folder, never in the game folder; when that sits on another disk, fold both) and key every remote, local and last-sync path the way the disk does. - A local file spelled differently from Rosebud's is the same file: the three-way rules apply to it, so a locally edited NFD-named file is kept, and is a conflict once Rosebud changes it too. Rosebud's text lands on it under its local name. A difference in letter case counts as a rename, compared with the last sync like any other change. - Two Rosebud names that would land on one file or folder here (Foo.js and foo.js, Scripts/ and scripts/) are each a name_collision: neither is written, even with --force, Rosebud's copies are saved apart in .remote, the saved version stays put and next_action says what to rename. - A Rosebud file whose folder exists here under another case is a case_conflict naming that folder, so it can't land under the old spelling. - pullInto now returns the next last-sync manifest itself, keeping a conflicted file's old entry by disk key. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…c to its folder A file deleted here and unchanged in Rosebud was silently kept deleted, so a pull into an emptied or different folder wrote nothing, said the folder held Rosebud's version, and the next update deleted those files from the game. - Pull lists such files under missing_locally, and next_action says the next update removes them from the game unless they are restored, naming the `rosebud pull ... --force` that restores them. --force now writes them. - The handle records the folder its manifest describes (absolute); a pull into another folder ignores that manifest, and an empty or missing folder counts as having no last sync, so every file is written. - A junk-named file Rosebud holds is ignored after the first pull, not reported as missing. - README: the update section pulls into the same ./dist folder the Quickstart publishes, documents missing_locally, and says pull empties the .remote folder each time. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…T/SIGTERM Agent harnesses kill commands that outlive their timeouts, and big publishes, updates, pulls and upload_retry waits now run for minutes, so a killed CLI left a lock that refused every rerun with state_locked. - A lock whose process id names no running process (kill(pid, 0) answers ESRCH) is taken over: it is moved aside, checked and removed before a new lock is created, so of two commands taking it over only one wins. A lock held by a live process, or naming none, still refuses; the message names the live process. - SIGINT and SIGTERM release every lock the process holds, print an `interrupted` JSON error saying what is safe to rerun, and exit 130 or 143. - README, help and CHANGELOG say long runs can take minutes, that rerunning an interrupted update, pull or assets create is safe, and that an interrupted publish may have created the game. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A 503 sync_unavailable is a known refusal: outcome rejected, exit 3. - A failure while creating the upload session no longer says the operation "may have completed": nothing was released, so a lost or garbled answer is not_sent and a server error is rejected, and an update's saved operation is cleared. - Signed uploads send any header the session lists except credentials and connection headers (authorization, cookie, proxy-authorization, host, connection, content-length, transfer-encoding and the other hop-by-hop ones), with names checked as tokens and values without line breaks. - changed_paths lists of up to 4,000 paths (before and after, two per game file) are kept; the README says so. - release_upload_limit on a first publish says to publish part of the media and update with the rest. - key_expired and insufficient_scope during assets create say to rerun the identical create after the new login is approved; required_scope is bounded to 32 letters a side; the malformed-scope test checks the full stderr and the publishing shape. - Messages name --timeout-ms and --api-base instead of library options. - On Windows, pull skips names Windows would trim or send to a device; pull warns about hidden entries, which update refuses. - README: "The login works with…", .oga in the media list, the long upload_retry wait, sync_unavailable, and the exit table's not_sent case. The name-rule message gives the 255-character limit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e disk When the folder legitimately held both Foo.js and foo.js and Rosebud changed one of them, the last-resort case check found the other name as a rival and refused the write, even with --force. A write whose target is itself a local file lands on that file, so it is never a rival's. Adds a test that runs only on case-sensitive disks. The planPull unit test now spells its NFD and NFC names as escapes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A pull with conflicts leaves the saved version where it was, but for a game just connected with --project that version is already Rosebud's, so a plain update went through and, after a name_collision, deleted files from the game. - Pull records its unresolved conflicts (paths and the version it read) in the handle; update refuses with pull_conflicts (exit 2) until a pull reports none or --expected-version names the version, and next_action gives the exact command. A successful update clears the record. - Update from another folder than the last sync's (handle.directory) is refused with directory_mismatch unless --expected-version is given, since releasing it as it is would undo what that sync brought in; next_action says to pull there first. - Without a last sync for the folder, pull's conflict sentence says the files differ and no last sync can tell which side changed them, naming where the last sync was. The restore hint beside conflicts says --force also settles them for Rosebud, discarding those local edits. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d probe edges - Takeovers of a stale lock run under a `.lock.takeover` guard created with wx; under it the lock is read again, so two commands can never both replace it (8 and 16 contenders, 90 rounds: never two holders). A guard left by a killed takeover is stale the same way as a lock. Locks are recorded as held the moment they are created. - A lock naming this process's own id that it doesn't hold is stale (an earlier process with the same id). - The `interrupted` error says a lock was released only when one was held. - The disk probe names its NFD and NFC spellings as escapes, and if it can't write its scratch file the pull folds both case and form instead of failing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… takeover unwedgeable - The handle saves the folder's real path, and update and pull compare folders by real path, or by identity on disk for another letter case, so `dist`, `./dist/`, its absolute spelling through /var on macOS or a linked parent folder are the same folder and never refused with directory_mismatch. - Locks are written aside and hard-linked into place with the process id and a fresh token, so they are never empty (an empty one older than 10 s, from a disk without hard links, counts as stale). A stale lock is removed only by the command that creates its own claim file, `<lock>.<token>.claim`; no other command can remove it, so it is still there when that command looks again, and a claim left by a takeover that was itself killed is stale and taken over the same way. The single takeover guard, which an empty file could wedge, is gone. Releasing a lock that was removed meanwhile is not an error. - pull_conflicts' next_action names `--zip <file>` for a ZIP update. - With no last sync at all, pull says the files differ and no last sync can tell which side changed them, instead of "changed both here and in Rosebud". Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hile writing the site docs - Tests that check an absolute path the CLI reports (rosebud_copy, the copies folder, the handle's folder) compare both sides with realpathSync.native, so Windows' 8.3 temporary paths (RUNNER~1) match the long form the CLI reports. This fixes the Windows CI failure of the case-only rename test. - The pathList comment says the bound is twice the per-game file limit: Rosebud names each changed path once, from both versions' files. - README documents `rosebud login --name <label>` and that empty media files are refused. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lock edges Any link failure but EEXIST now falls back to the in-place exclusive create (Windows exFAT/SMB report EISDIR/EINVAL), so claim files get it too. An unreadable lock is held, never an empty one to take over. The lock race test tolerates scanner leftovers on Windows, sameFolder ignores a zero inode, and two doc nits are fixed. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…match held paths in either Unicode form - Overlap between --directory, the state file and Rosebud's copies is checked after resolving links, so a folder reached through a linked parent is never the copies' folder that pull empties. - Pull downloads and checks every file it writes before removing anything, so a failed download during a rename by case leaves the old name in place and a rerun finishes the rename instead of reporting local_deleted. - Update matches the paths Rosebud holds as text or media in either Unicode form, as pull leaves local names, so an unchanged editor SVG stays text. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nswers by file count, and send Rosebud's spelling of held names - Pull rehashes every file it would replace or remove, and checks that no file appeared where it writes, after the downloads and before touching the folder. A change made meanwhile stops it with folder_changed, leaving the folder as it was. - An upload session's answer may exceed 1 MiB by 8 KiB per declared file, since each upload carries a signed URL; 1,200 files no longer fail as unexpected_response. - A local file matching a path Rosebud holds only by Unicode form goes up under Rosebud's spelling, read from the local name, so an unchanged NFD media file is linked rather than refused as noncanonical_path. New media goes up in NFC, and two local names that are one name in NFC are refused as duplicate_path. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ed, and retry an update through its saved upload session - A takeover removes a stale lock only while it is the same file (text and modification time) and still stale, so on a disk without hard links it never removes the empty lock another takeover has just created. - Media is read again for upload without following links, and sent only when its size and MD5 match what was packed; otherwise file_changed, with nothing sent. - update saves its upload session with the pending operation, and an identical rerun sends that session again, so Rosebud matches the earlier attempt even when a new session would be refused. A session Rosebud no longer takes is replaced by a new one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t sent them An update whose media went in one ZIP, from a Rosebud that took no upload session, could not be replayed once Rosebud took sessions: the retry declared its media in a new session, so Rosebud saw a different request under the same operation and answered operation_reused. The pending operation now keeps how its media went, one ZIP or an upload session, and an identical rerun sends them the same way. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
rosebud-agent-cerulean-fox
left a comment
There was a problem hiding this comment.
NanoMAI review
Reviewed commit: db17024c75206b444cc06170d61b7aa40453706e
nanomai review — two actionable findings, not an approval
Reviewed the complete 13-file diff for rosebudai/rosebud-cli#5 at db17024, against real merge base 7a5d793. The requested intake SHA, initially selected SHA, final fetched head and final PR metadata head all match. The base branch head is 7a5d793a91da226cfa9caa73ed36ce98081856ef. No source was edited, committed or pushed; the tracked worktree is clean.
Findings
- Unicode-equivalent filenames do not round-trip on Linux. Pull can manufacture duplicate copies of a single server path and prevent further updates, even after the user follows the recovery command. The first inline comment includes text and media reproductions.
- Downloads without stored checksums ignore their known size. A shorter-than-advertised successful download can replace an asset and become the saved sync baseline instead of being rejected. See the second inline comment.
Scope and before/after behavior
This adds pull for editor-made games, with three-way comparisons, separate remote conflict copies, a guard on unresolved conflicts and a folder-specific sync baseline. Reading files back requires the owner’s code-download entitlement; publishing and updating remain free. Directory media now uses upload sessions and up to four signed PUTs at once instead of having to fit in one 10 MiB ZIP; unsupported servers retain the one-ZIP fallback. New limits are 19 MiB per file, 2,000 files/1 GiB per game, a text ZIP of 10 MiB compressed/50 MiB expanded/500 entries, and the server’s current 128 MiB ceiling on newly staged media. Login scopes and inactivity expiry diagnostics, replay recovery and stale-lock/signal handling are also included. The package remains 0.1.0; this PR is not itself an npm release.
Independently executed verification
- Environment: Linux, case-sensitive and Unicode-form-sensitive filesystem; Node v22.23.3, npm 10.9.9.
npm cipassed (5 packages installed; audit reported 0 vulnerabilities).npm run checkpassed, andnpm testpassed including its TypeScript build: 166 tests, 159 passed, 7 skipped, 0 failed. The seven skips are five case-insensitive-disk and two Unicode-form-insensitive-disk integration cases. The same typecheck, formatting check, build and suite also passed when invoked directly through Node. Initial unmodified npm script execution hit the executor’s/workspacenoexec mount; temporary executable shims outside the repository invoked the same installed TypeScript/Prettier scripts through Node, after which the actual npm gates passed. No source or package scripts were changed for that workaround.git diff --check 7a5d793a91da226cfa9caa73ed36ce98081856ef db17024c75206b444cc06170d61b7aa40453706epassed.- Three additional isolated regression probes against the built CLI and bundled fake server failed their expected-correct-behavior assertions, reproducing the two findings: NFC/NFD media matching (including the advised force/update sequence), a locally edited NFD text file receiving an NFC duplicate, and a known-size/null-MD5 download accepting 15 bytes for an advertised 16. The committed suite does not catch these. Probe source and logs are preserved under
/tmp/nanomai-pr5-review/; temporary game folders and local fixture servers were cleaned up.
GitHub evidence and access
mai-github-checks rosebudai/rosebud-cli db17024c75206b444cc06170d61b7aa40453706e --pr 5 was run after selecting the head and refreshed immediately before this proposal. Final observation: 2026-10-28T15:02:34.658448+00:00; reader identity rosebud-agent-cerulean-fox; aggregate state passed; exact reviewed SHA db17024. Evidence: checks for the reviewed commit.
checks:repos/rosebudai/rosebud-cli/commits/db17024c75206b444cc06170d61b7aa40453706e/check-runs, HTTP 200, credential github_app_installation (rosebud-agent-checks; app 5113875, installation 165937284), statepassed.- test (windows-latest): state
passed, statuscompleted, conclusionsuccess. - test (ubuntu-latest): state
passed, statuscompleted, conclusionsuccess.
- test (windows-latest): state
statuses:repos/rosebudai/rosebud-cli/commits/db17024c75206b444cc06170d61b7aa40453706e/status, HTTP 200, credential reviewer_pat, stateno_checks.- Empty result; this surface has no checks, not a passing check.
No check/status endpoint was unavailable or access-denied; both were readable. The startup receipt concerns a different revision and was not used as reviewed-head evidence. These CI results were read from GitHub, not independently executed Windows checks.
The PR metadata endpoint (repos/rosebudai/rosebud-cli/pulls/5) returned HTTP 200 on both initial and final reads; the PR is open, non-draft, and its head did not move. Its requested-reviewer and requested-team arrays are both empty. The reviews endpoint returned HTTP 200 and an empty list. This proposal does not establish that a reviewer request still exists: the coordinator must verify publication eligibility and existing approval rules; no GitHub review write was performed by this executor.
Validation limits and author-reported evidence
I did not independently run a real Gateway integration, macOS/Windows filesystem tests, real-user gameplay or production traffic. The PR description’s macOS/Node 22/24 test results and local-Gateway 56 MB publish/conflict/pull/one-PUT/merge flow are author-reported, not independently reproduced receipts. Passing repository/CI checks does not establish release readiness, dependent Gateway deployment, live sync enablement, merge, deployment or npm publication. Preserve the release prerequisite: do not tag 0.2.0 until the dependent Gateway changes are live and sync is enabled in Live.
| foldsCase = foldsForm = true; | ||
| } | ||
| return (path) => { | ||
| const form = foldsForm ? path.normalize("NFC") : path; |
There was a problem hiding this comment.
[P2] Match the server’s Unicode-equivalent paths on Linux too
On a form-sensitive disk this returns the raw spelling, but the upload/server path identity is NFC. Reproduced on Linux with the bundled fake (nfc: true): publish assets/cafe\u0301.png, change the remote assets/caf\u00e9.png, then pull. The unchanged local asset is reported as local_deleted and local_only; following the suggested pull --force creates both spellings, reports synced, and the next update fails with duplicate_path. A locally edited NFD text file similarly gets a second NFC copy on a plain pull, so even the advised --expected-version update is refused. Match the server’s NFC-equivalent logical paths to the existing local spelling separately from disk case rules, while still detecting genuinely duplicated local names. Add Linux text/media round-trip tests that go through the subsequent update.
There was a problem hiding this comment.
Fixed in d0b1225. Names are now matched regardless of Unicode form on every disk, because Rosebud names files in NFC. Letter case still follows the disk. Rosebud's change lands on the local file under its local spelling, so no second copy is created. Two local names that differ only in form are still refused with duplicate_path on update. The Unicode-form pull tests now run on every platform, including Linux, instead of skipping where the disk keeps forms apart. Each goes through a later update. A new test covers your media case: publish an NFD name, change it remotely, pull, then update.
| const to = join(incoming, randomUUID()); | ||
| await api.downloadAsset({ url: file.url, path: to, overwrite: true }); | ||
| const md5 = await md5File(to); | ||
| if (file.md5 !== null && md5 !== file.md5) throw corrupt(path); |
There was a problem hiding this comment.
[P2] Validate the known size when a download has no checksum
GameFiles.media can provide md5: null with a known byte size, but the download count is discarded and this check then verifies nothing. With the existing fake configured as { hideMd5: true, damageMedia: true }, a manifest advertising a 16-byte replacement returns a 15-byte HTTP-success body. Pull exits successfully, replaces the local asset with those incomplete bytes, records their digest, and reports synced. Check every known size against the staged download before changing the game folder, and reject a mismatch as download_corrupt without advancing the handle or replacing the existing file. Please cover the known-size/null-MD5 case, not only corrupt downloads with an MD5.
There was a problem hiding this comment.
Fixed in d0b1225. Every download is now checked against its known size as well as its MD5, before the folder changes. A mismatch fails with download_corrupt, leaving the file and the handle unchanged. Added the known-size/null-MD5 test ({ hideMd5: true, damageMedia: true }).
…a download's size when it has no checksum On a disk that keeps Unicode forms apart, as on Linux, pull compared names as the disk spells them, while Rosebud names every file in NFC. A file published from a decomposed name then looked deleted locally and new remotely; --force wrote a second spelling, and the next update failed with duplicate_path. Names are now matched regardless of form everywhere, and Rosebud's version lands on the local file under its local name. Letter case still follows the disk. A media file Rosebud lists without an MD5 was checked against nothing, so a short download could replace the local file and become the sync baseline. Its known size is now checked, and a mismatch fails with download_corrupt before the folder changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
rosebud-agent-cerulean-fox
left a comment
There was a problem hiding this comment.
NanoMAI review
Reviewed commit: d0b1225e58f8ac7c1b4bb100d81e952322dd6311
nanomai rereview — both findings resolved
Reviewed d0b1225 for rosebud-cli PR #5. This is a focused rereview of the entire three-file follow-up diff from db17024, with the affected pull planner, upload path matching, download implementation and regression tests traced in context. The PR merge base and current main head remain 7a5d793. I found no remaining actionable issues in these follow-up changes and recommend approval of the code.
Earlier findings
- Unicode-equivalent filenames: resolved. Matching always uses NFC identity while preserving disk-specific letter-case rules. Remote changes reach the existing local spelling rather than manufacturing a duplicate. Independently verified local text edits, a later editor conflict and merged update using
--expected-version; media pull, force rerun and an edited upload; and new remote files entering an existing Unicode-equivalent parent directory. The committed suite also verifies that genuinely duplicated local names remain refused by update. - Known-size downloads without an MD5: resolved. Every available size is checked against the staged download's byte count, in addition to any available checksum, before modifying game files. Independent shorter- and longer-than-advertised null-MD5 cases returned
download_corrupt, preserved the original media, text, directory contents and state file, and left no staging leftovers. Clean retries then succeeded.
Independently executed validation
- Linux, case-sensitive and Unicode-form-sensitive disk; Node v22.23.3, npm 10.9.9.
npm cipassed with 0 reported vulnerabilities. npm run checkpassed (typecheck and Prettier);npm testpassed, including its TypeScript build: 168 tests, 163 passed, 5 skipped, 0 failed. All five skips require a case-insensitive filesystem. The Unicode regressions now execute on Linux instead of skipping.- Executor limitation: ordinary npm script attempts could not execute
tscfrom the/workspacenoexec mount. The actual npm check/test gates were then run with a temporary script-shell wrapper and executable shims outside the repository, invoking the same installed TypeScript/Prettier entrypoints through Node. No package scripts or source files were changed for this workaround. - Six additional isolated regression probes all passed on this head. The same probes all failed their expected-correct-behavior assertions on an independently compiled archive of
db17024c75206b444cc06170d61b7aa40453706e; these were behavioral failures, not setup failures. Probe source and before/after logs are retained under/tmp/nanomai-rereview-5/for this executor session. git diff --check 7a5d793a91da226cfa9caa73ed36ce98081856ef d0b1225e58f8ac7c1b4bb100d81e952322dd6311passed. No tracked files were edited, committed or pushed. Temporary fixture servers and game folders were cleaned up; the tracked worktree is clean.
Refreshed GitHub evidence
mai-github-checks rosebudai/rosebud-cli d0b1225e58f8ac7c1b4bb100d81e952322dd6311 --pr 5 was run after selecting the head and refreshed immediately before preparing this proposal. Final observation: 2026-10-28T15:20:13.312897+00:00, reader identity rosebud-agent-cerulean-fox, aggregate state passed. Reviewed-commit checks.
-
test (ubuntu-latest):
passed, statuscompleted, conclusionsuccess. -
test (windows-latest):
passed, statuscompleted, conclusionsuccess. -
Checks endpoint
repos/rosebudai/rosebud-cli/commits/d0b1225e58f8ac7c1b4bb100d81e952322dd6311/check-runs: HTTP 200, statepassed, credentialgithub_app_installation(rosebud-agent-checks, app 5113875, installation 165937284). -
Statuses endpoint
repos/rosebudai/rosebud-cli/commits/d0b1225e58f8ac7c1b4bb100d81e952322dd6311/status: HTTP 200, stateno_checks, credentialreviewer_pat. Its list is empty; this is not a separate passing check.
Neither evidence endpoint was unavailable or denied. The startup capability receipt was not used as reviewed-head evidence. The final PR metadata read succeeded (HTTP 200): open, non-draft, unchanged head d0b1225. Requested reviewers and teams are empty. This proposal is in response to the current human rereview request, not the older automatic request. No GitHub review write was performed by this executor; publication remains for the coordinator to verify and perform.
Limits and release prerequisites
I did not independently rerun a real Gateway integration or macOS/Windows filesystem behavior. Windows success above is GitHub CI evidence, not a local Windows run. Approval of this code does not verify dependent Gateway deployments, live sync enablement, merge, deployment or npm publication. The existing release prerequisite remains: do not tag 0.2.0 until the dependent Gateway changes are live and sync is enabled in Live.
Summary
This lets an agent work on any game a person made in Rosebud, not just games the agent published, and removes the 10 MiB ZIP limit. It also adds clearer errors for scoped and expiring logins.
rosebud pull --directory <folder> [--force]brings a game's current files, including edits made in the Rosebud editor, into a folder..rosebud/project.remote/), never inside the folder.updatestays refused until you merge and pass--expected-version.--forcerestores them.--forceonly settles conflicts; it never discards changes made only locally.publishandupdate.updateafter a lost response replays the same operation without uploading again. A "busy" answer is treated as not applied. While Rosebud is still checking a game's existing files, the CLI keeps retrying for as long as the count of files left to check keeps falling.next_action. A conflict also giveschanged_paths, and a size error giveslimitas a number. An expired login (key_expired) says to runrosebud login. A missing scope (insufficient_scope, withrequired_scope) says to log out and back in.whoamishows the login'sscopesandexpires_at..env, credentials and dependency folders stay refused..DS_Store,Thumbs.db,desktop.ini) is skipped..svgand.gltffiles that Rosebud holds as text stay text.package.jsonstays at 0.1.0; the version bump belongs in the release PR.Depends on
These Gateway PRs must be live before a release that uses them:
limit,upload_retrywithremaining.Don't tag 0.2.0 until those are live and sync is turned on in live. Until then,
pullanswerssync_unavailableand uploads fall back to one ZIP.How to test
npm teston macOS: 146 tests, 144 passing, 2 skipped (case-sensitive-disk tests).npm testin Linux containers (Node 22 and 24): 146 tests, 140 passing, 6 skipped (case-insensitive-disk tests and one root-only skip).tsc --noEmitand Prettier are clean. This PR's CI runs the suite on Linux and Windows.test/fake-rosebud.mjs): sessions, signed PUTs, pull, conflicts, collisions, busy, upload_retry and scopes.version_conflictwithchanged_paths.--expected-version.🤖 Generated with Claude Code